1
\$\begingroup\$

I'm creating an online arena game server (moba) that players can play together. 2v2 or 3v3. The game is similar to Battlerite.

I chose client/server architecture with deterministic server that tells clients about positions, actions etc. I assume everything is OK and players have good connection, so for now i don't care about prediction or lag compensation. I also use static state update -- server sends all needed data to players even if a player didn't move it sends his position, also spell cast data are in state update too. I use UDP for my network backbone and 50ms tick rate on server side. in client side we use interpolation between two update to make game seems smooth . If command to move arrives after a period when you get next update you will move.

here is my client code:

    public void loop()
    {
        while (client_started)
        {
            try
            {
                receive();
            }
            catch (Exception e)
            {
                throw e;
            }
            try
            {
                send();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
    private void send()
    {
        if (disconnected)
            return;
        try
        {
            foreach (var item in need_ack_packets)
                send(item.Value);

            on_sampling?.Invoke(command_sample);
            if (command_sample.has_data)
            {
                send(command_sample.to_udp_packet());
                command_sample.clear();
            }
        }
        catch (Exception e)
        {
            throw e;
        }
    }
    private void receive()
    {
        try
        {
            var data = client.Receive(ref receive_endpoint);

            if (data.Length == 0)
            {
               //------
            }

            var pack = udp_packet.from_bytes(data, aes_crypto);

            if (pack.is_ack)
                need_ack_packets.TryRemove(pack.ack_to_message_id, out _);
            if (pack.need_ack)
                send_ack(pack);
            if (pack.has_body)
               {
                    on_data_receive?.Invoke(this, pack.body);
                }
        }
        catch (Exception e)
        {
            throw e;
        }
    }

As you can see on the client side I send data after each server update receive and if an important packet must be sent I send it again and again until server ACKs for that (I send sample in 50 ms rate too). The movement or attack is not important for me but spells are. so i can make sure all important data will reach to server with ACK so in worst sample maybe we have delay.

But my problem is on the server side. Let me give you an example :

  • player no. 1 sends a spell/skill cast -- assume it's a powerful strike with a sword or a roll forward.
  • the packet is dropped.
  • client sends it again.
  • server receives it and ACKs. (100 ms delay until now)
  • in next update server sends to all that player no. 1 casts a spell.

  1. what if our own player didn't get this update ? (as I said there is no prediction so he's waiting until server's command)
  2. what if another player didn't get this update ?

I searched a lot about this subject but can't find answer to this scenario. All documents talk about movement and if an update drops, "don't worry it will be sent soon again (50 ms latter)".

Lets say the spell is roll so if server receive that command will change player position forward for 1 second (a bit in each tick). If a player miss two updates he has a new position but he missed spell cast information. Lets say the spell is powerful strike it must happen in 1 or 2 update so if a client miss two updates he missed it or if we resent spell cast to unlucky client (if attacker and target move) in new update client sees attacker and target are in new positions and there is a spell cast request in update too so powerful strike can't cast on target due to distance between them.

please correct me if I made mistakes in any step of my work.I'm searching for an efficient way to handle these scenarios. how to send important update (repeat whole update, repeat important parts)? how play animations attacks etc to make game smooth? how handle new positions and delayed spell casts?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Related: gamedev.stackexchange.com/questions/75580/… also: imgur.com/HTp69Du taken from "Distributed Systems Ed. 3" [Tanenbaum, Steen]. I also recommend looking for ready client-server library that has it already sorted out. For the simplest solution - I would hold both TCP connection for important data and UDP for fast, frequent data. \$\endgroup\$
    – Tooster
    Jul 22, 2021 at 12:00

1 Answer 1

2
\$\begingroup\$

One approach I've seen is to include in each packet an ID of the last update you received from this client/server, or a bitmask of which packets you've received in a sliding window.

When forming an update packet to send, you include the current state info, plus any "important" messages from any packet for which you don't yet have a confirmation of receipt.

That might mean you end up sending "I cast spell X at timestep Y" 2-3 times before the first one gets acknowledged, but that and the acknowledgment ID/mask are only a few bytes of extra bandwidth if you use your bits efficiently, rather than a whole separate stream of packets on their own. And because you repeat yourself "eagerly", a dropped packet will usually incur only a single packet interval of latency, rather than 3 x your one-way communication delay (to wait for the initial receipt, then note the missed acknowledgement and re-transmit, then receive the re-transmission on the other side).

By including the time stamp when the important action occurred, you can ensure it's not double-counted when two copies of it are received. And you can use that to back-date its effects when received late, fast-forwarding the animations or other feedback to get back on track with the consensus timeline.

Obviously to prevent cheating, your server will validate that a back-dated event like this lines up with a gap where a packet failed to arrive, and keep track of clients who are dropping a lot of packets this way: they're either on a very poor quality connection, or are cheaters strategically skipping packets to try to exploit the back-dating, and either way they should probably be dropped from the match.

\$\endgroup\$
3
  • \$\begingroup\$ thanks for answer. I'm searching for an efficient way to handle command "Player X cast spell Y on direction Z" i can make sure that command Reaches to server with ACK. but I have no Idea what is best way to tell others. may i repeat one server update 3 time or repeat important information of that update 3 time until get ACKS from all players? if spell is roll if we lost first state at next state we have new position and roll spell cast data so unlucky client sees roll happen in next position. or if spell is strike if we lost packet the animation should finished on next update so it missed. \$\endgroup\$
    – Mamad R
    Jul 22, 2021 at 15:42
  • 2
    \$\begingroup\$ Because the event is sent with a time stamp, you can back-date it to the position where the character was at that time, even if you don't hear about it until the 3rd retransmission. Or you can include the casting position as part of the message if you don't want to rely on accurate rewinding. \$\endgroup\$
    – DMGregory
    Jul 22, 2021 at 15:49
  • \$\begingroup\$ I find good information in your answers. I corrected my question a bit. can you suggest me tutorial or video that can help me about those scenarios in my game? (I mean when client receive spell cast late and how handle animation, new positions, new spells on client also how re transmitting important information on server side) \$\endgroup\$
    – Mamad R
    Jul 22, 2021 at 16:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .