#Not fully understanding where the server is actually controlling the ammo on lesson 185.

1 messages · Page 1 of 1 (latest)

kindred lodge
#

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?

#
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();
}
celest steeple
#

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
     */
}```
kindred lodge
#

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?

celest steeple
#

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

kindred lodge
#

So is it now replicated through the ClientRPC?

#

We took out the ReplicateUsing from ammo in this video and removed it from getlifetimereplicatedprops

celest steeple
#

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

kindred lodge
#

Sorry I guess I'm still confused, in this video we remove Ammo from GetLifetimeReplicatedProps so its no longer replicated that way

celest steeple
#

let me check my code

kindred lodge
#

Thats the gitcommit for the lecture

celest steeple
#

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

kindred lodge
#

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

celest steeple
#

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?

kindred lodge
#

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

celest steeple
#

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

kindred lodge
#

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?

celest steeple
#

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

kindred lodge
#

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

celest steeple
celest steeple
#

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

kindred lodge
#

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!

celest steeple