So before lesson 185 we were having the server call spend round by doing a HasAuthority() check before calling spend round. In this lesson we removed this and the ammo replication and used client rpcs to ultimately update the ammo. But according to the code, or how I'm understanding it, the client is still ultimately controlling their ammo and its never actually authenticated by the server. You no longer need authority to spend a round, only to call the client rpc. To me this looks like the client has full control over their ammo, what am I not understanding?
#Not fully understanding where the server is actually controlling the ammo on lesson 185.
1 messages · Page 1 of 1 (latest)
void AWeapon::AddAmmoToMag(int32 AmmoToAdd)
{
Ammo = FMath::Clamp(Ammo + AmmoToAdd, 0, MagCapacity);
SetHudAmmo();
ClientAddAmmo(AmmoToAdd);
}
void AWeapon::ClientAddAmmo_Implementation(int32 AmmoToAdd)
{
if (HasAuthority()) return;
Ammo = FMath::Clamp(Ammo + AmmoToAdd, 0, MagCapacity);
OwnerCharacter = !OwnerCharacter ? Cast<APVPocalypseCharacter>(GetOwner()) : OwnerCharacter;
if (OwnerCharacter && OwnerCharacter->GetCombatComp() && IsFull())
{
OwnerCharacter->GetCombatComp()->JumpToShotgunEnd();
}
}
void AWeapon::SpendRound()
{
if (Ammo > 0) Ammo--;
SetHudAmmo();
if (HasAuthority())
{
ClientUpdateAmmo(Ammo);
}
else
{
Sequence++;
}
}
void AWeapon::ClientUpdateAmmo_Implementation(int32 ServerAmmo)
{
if (HasAuthority()) return;
Ammo = ServerAmmo;
Sequence--;
Ammo -= Sequence;
SetHudAmmo();
}
here's my notes on SpendRound
//called when? we fire the weapon
void AWeapon::SpendRound()
{
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
//need to access player controller to change the ammo on the hud
//weapon will spend rounds very quickly
//instead of getting theowner and casting it to a blaster character over and over
//make a variable for the character & controller
SetHUDAmmo();
/*
* We're setting ammo locally, but because we're firing a round both on client and server
* it'll be set on the server too
* if we're on the server, we need to send that info to client
* so te client knows the authoritative value of ammo
*/
if (HasAuthority())
{
ClientRPCUpdateAmmo(Ammo);
}
else if (BlasterOwnerCharacter && BlasterOwnerCharacter->IsLocallyControlled())
{
/*We've just spend a round and the server's replication of it hasn't yet reached us*/
Sequence++;
}
}```
so we're using client side prediction
letting the client predict what the ammo will be and then if it's wrong, the server can correct it with ClientRPCUpdateAmmo()
And we use sequences to keep track of when the client has last been updated by the server
here's my notes on how this works with the maths
/*When we update our client ammo, we need to perform a correction
* because we may have spent rounds rapidly
* so we need to keep track of how many times we've fired the weapon
* since we got our last update
* for ammo: each update corresponds to one round so we don't need to store the value of ammo for each update
* we can just store a sequence number which we can check and that number will represent the number of
* unprocessed server requests
*/
void AWeapon::ClientRPCUpdateAmmo_Implementation(int32 ServerAmmo)
{
/*make sure we're not on the server*/
if (HasAuthority()) return;
/*Server's replication of ammo has reached us here*/
/*Set our ammo to server ammo*/
Ammo = ServerAmmo;
/*We know we've just recidved a processed server response
* so we can decrease the sequence as one of our unprocesed server
* requests has now been processed
*/
Sequence--;
/*Sequence represents how many rounds we've spent that the ammo has
* not yet replicated back to us
*/
Ammo -= Sequence;
SetHUDAmmo();
/*Ammo: 10
* Fire 2 rounds: Client: Ammo: 8 Sequence: 2
* Server: Ammo: 9
* Client: Ammo = ServerAmmo = 9; Sequence: 2-1 = 1, Ammo - Sequence (1) = 8
*/
}```
Those are good notes thank you. I'm still not understanding where the server is controlling the ammo though. Please tell me where I'm incorrect. I'm going to put comments in my code from above to show my thinking.
void AWeapon::SpendRound()
{
//SpendRound is called from Fire() and before this video it had a HasAuthority() check
//We Removed the HasAuthority() check meaning that now SpendRound() is called locally
//This means that:
if (Ammo > 0) Ammo--; //The client has now subtracted thier own ammo locally
SetHudAmmo(); //The client has now set thier ammo in the hud
if (HasAuthority())
{
//The server has now called this AFTER ammo has been set by the client
//The value passed in here for Ammo is now what the client says thier ammo is
//So where is the server authenticating this?
ClientUpdateAmmo(Ammo);
}
else
{
Sequence++; //The sequence is qued
}
}
Ammo was a replicated variable before and spend round used to only be called by the server via the HasAuthority() check but it is no longer that way after this video, so is the server still actually controlling this?
Right here in SpendRound() Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
that is called on both the server version of your weapon and client version
whenever you click the fire button to shoot a bullet
both the server and client call this method
the client calls it first because they're locally pressing the button on their PC
then when the server eventually calls the same one, Ammo is a replicated variable so it will override the clients one. And to keep everything in sync with what the client is now at (as they may have kept pressing fire by the time the Ammo gets replicated back), we use the sequence numbers
So is it now replicated through the ClientRPC?
We took out the ReplicateUsing from ammo in this video and removed it from getlifetimereplicatedprops
No it's just replicated because it's being called anywhere the server can call it. In GetLfieTimeReplicatedProps, it says this is a replicated variable so anytime it changes, the server will replicate it down to the client. Doesn't matter where it's called as long as the server has the ability to change Ammo, it'll eventually override the client version of Ammo
E.g if you only called it if (!HasAuthority) Ammo = 2. then that would only be local
but if you called Ammo = 2, that would be set on client and server
locally first, then server would just override it again to be 2
but if it's if (HasAuthority) then server would set it first, then client would recieve it later
you can play around with it and print out the values
try the different checks and see what happens in game
oh we did?
Sorry I guess I'm still confused, in this video we remove Ammo from GetLifetimeReplicatedProps so its no longer replicated that way
let me check my code
Thats the gitcommit for the lecture
AHH you're right
okay so one sec
then in here ClientRPCUpdateAmmo_Implementation(int32 ServerAmmo)
ServerAmmo is being passed in as a parameter
and set here Ammo = ServerAmmo;
client rpc is being called on the server and executed on the client
so the server is sending down the correct ammo
then the client updates its local ammo
then adds on the sequence numbers
in case it fired more locally (i.e unprocessed fires)
sorry about that
But that is being called after the client sets their ammo locally, so that seems like the client is in control of their ammo amount and not the server. The server was prior to this video because spend round was only called on the server and then replicated to clients
void AWeapon::SpendRound()
{
if (Ammo > 0) Ammo--;
SetHudAmmo();
if (HasAuthority())
{
ClientUpdateAmmo(Ammo);
}
else
{
Sequence++;
}
}
The value of ammo passed into client update ammo is the value after the client locally changes thier ammo count
It's using client side prediction, so the client is updating its own ammo first, then the server will correct them if they're wrong. Same thing happens with player movement. The client moves first with their WASD keys, then the server will correct them if they're wrong. It helps a game play more smoothly so you don't always wait for the server to dictate every move, otherwise you'd be waiting 50 - 200ms to move / see the updated ammo count
ultimately the server is always in charge of the important parts
and right here it seems to be just updating the HUD visually. I don't remember the checks that happen before pressing fire in the first place
this section you're up to is probs the lag compensation section yeah?
client side prediction for ammo?
But where is the server controlling the ammo? Is it in another function? Because in this one the client is controlling their ammo? And yes
in SpendRound() the server version of the weapon calls Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
And then calls ClientRPCUpdateAmmo(Ammo); and passes down the new ammo as a parameter
and all that happens after the client version of the weapon does it
as it takes 50-200 ms for the server to know the client did anything
you have great questions btw, I had the same ones lol
what you can do to learn the flow is to breakpoint at Fire() then tab through all the code as a server vs client and you see all the code executing in order
I eventually did that for all the firing as I got super confused about what is calling what and in what order
then I would write out the entire flow from start to finish in a google doc to understand the stack
Here is a way to split if you're the server vs client too
Maybe I need to look through all of the functions in AWeapon then and see where the misunderstanding is.
Here is my understanding set up in a different way.
Before this video the server was in full control of the ammo because in Fire() we had this check
if (HasAuthority()) SpendRound();
This meant server called spend round and when the ammo value was changed it was replicated to all clients via OnRep_Ammo(). So the server was in full control of the ammo count and then let the clients know what the ammo value was.
Now in this video we removed the above HasAuthority() check from Fire() so the client could update their ammo immediately and call SpendRound(). In SpendRound() the only HasAuthority() check is for the ClientRPC.
void AWeapon::SpendRound()
{
if (Ammo > 0) Ammo--;
SetHudAmmo();
if (HasAuthority())
{
ClientUpdateAmmo(Ammo);
}
else
{
Sequence++;
}
}
This means the client has now called this
if (Ammo > 0) Ammo--;
and then after the client has decremented their own ammo and that new ammo value is then passed in to the ClientRPC meaning that the RPC is taking this value that has been altered by the client and ultimately the server does not control ammo anymore like it did prior to this video.
I appreciate you taking the time to work with me, I really want to understand this stuff not just copy and paste lol
So I guess is there somewhere else the server is controlling the ammo that I am missing? In another set of functions outside of these ones?
Also is there a tutorial you'd recommend for setting up the breakpoints like that?
Yeah you're letting the client update their own ammo locally, then the server will override it later. I'm not sure the ramficiations of that. E.g you only allow the client to fire if there's enough Ammo in !EquippedWeapon->IsEmpty() in CombatComponent but that ammo is now local. There's other checks there too in CanFire(). You might have to breakpoint through it or play around with changing the ammo locally on the client in the editor and seeing what happens
When I setup breakpoints I used VS code and watched hours of YT vids to get that to work (was confusing). But for jetbrains rider (when I switched) it was super easy. For visual studio, stephens new course has a guide on it, otherwise I would check out YT for whatever IDE you have
So ultimately then its kind of working in reverse now? The client is saying "I spent a round here is my new ammo count" and then the server is saying "alright I got your ammo amount and updated it". Before it was the client saying "hey I'd like to spend a round" and the server said "round was spent here is your new ammo". Is that the correct way of understanding it? It seems like we have determined that ammo is not a variable that needs to be fully controlled by the server if this is the case which seems like the opposite of what we were trying to do. Ultimately the whole purpose of this is for cosmetics, maybe Ill put it back to how it was then and make a function that only updates the hud on the client and not the actuall ammo
This is my first time really delving into multiplayer. I messed with it in Unity in the past but there were no truly good tutorials like this one for it so I never really got the full grasp on RPCs
For the first part that doesn't seem right as the server doesn't care what the client is doing as there's no ServerRPC here. It's the client updating its Ammo variable locally, then the server just says "hey here's the correct value" and the client says "okay cool, I'll reset my ammo to this value and add any sequence numbers to it so I'm up to the right local value"
totally get it, I started with unity too and just scrambled around with random videos lol.
multiplayer is hard, you're doing a great job!
what might help you is reading this
read all these articles, it helped wrap my head around client side prediction. It shows it with player movement, but you can apply the theory to the ammo here too
as the same idea is happening. Client updates itself locally, then server corrects it with the clientrpc and passing down its own server ammo value
I'll read that article. And thank you, this sever stuff is hard to wrap my head around sometimes. Single player its very easy to visualize and figure out why something isn't working. But adding multiplayer makes it much more difficult. I did just finish the first set of SSR videos where you can register a successful hit wit half a second of lag, that's some pretty cool stuff!
yess multiplayer is really hard, adding the server there is one extra mind game lol