6
\$\begingroup\$

What's the usual way to handle events that are checked both on server and the client, like attacking? There's a cooldown timer to an attack of which the client is aware (so to prevent spamming server requests and attack button is held).

Next there's a cooldown timer on the authoritative server. My problem is that sometimes attacks that end up firing on the client aren't executed on the server, because the server timer (serverFrameTime - lastAttackFrameTime > attackCooldown) didnt count up to the desired time, and so those are 'blank' attacks on the client.

Is there a standard way to handle that without emiting too many messages? This is a problem for me because i might be firing more of those events and more timers, so autoattacking without clicking is not a long term solution for me.

\$\endgroup\$
  • 1
    \$\begingroup\$ When you send the attack message to the server does it include the time it was sent according to the client? And does the server confirm to the client that an attack is made? I'd image that you need to sync the cooldowns between client and server and by including when the attacks were made in the messages could help. (Disclaimer I don't have much experience of networking but have read up on it a fair amount) \$\endgroup\$ – Malrig Feb 12 '16 at 9:02
  • 1
    \$\begingroup\$ i dont send that kind of information. my main reason for that is whatever a client sends to a sever can be altered, so vital information like attack time etc. can't be relied upon when client provides it. \$\endgroup\$ – TheAlmightyOne Feb 12 '16 at 11:35
  • 1
    \$\begingroup\$ I would recommend your server to tell the client when an attack has occurred so it knows when to go on cooldown. The client can then guess that the cooldown start at t = (t_clientRecievedReply + t_clientSent)/2 and adjust the cooldown time accordingly. Also maybe allow some commands to be "queued" on the server if the cooldown is close enough to finishing it will automatically execute when the cooldown ends (the message to the client would then have to let it know that the ability was queued so the client can guess the correct time of cooldown). \$\endgroup\$ – Malrig Feb 12 '16 at 11:49
  • \$\begingroup\$ i like your idea with quing premature attacks that were client validated anyway. i measured that with 150ping 1/20 consecutive attacks miss the cooldown by 5ms on average \$\endgroup\$ – TheAlmightyOne Feb 12 '16 at 17:58
  • \$\begingroup\$ One reason I would encourage you to include timestamps on all messages is that it allows the server to know what order actions were taken in. This link doesn't directly tackle your question but it does explain quite well how client side prediction (such that you want) can be implemented and some of the challenging problems. gabrielgambetta.com/fpm2.html \$\endgroup\$ – Malrig Feb 13 '16 at 8:43
4
\$\begingroup\$

The short of it is that your game design and art both need to be built around the realities of networked play.

When the user clicks, they should not immediately attack. Instead, the animation/effects should be a "pre-attack" visual that can be smoothly transitioned into a full attack animation/effect or a canceled effect. More over, this pre-attack should be of indefinite length; this allows the request to smoothly cover up any of the unpredictable network latencies.

Once this pre-attack animation is started, the client just waits for the server's authoritative notification that the attack has started or failed. The server should also allow some graceful flexibility in timings to account for expected inaccuracies, e.g. if the client's attack request comes in a few ticks before the timer expires then the server can just enque the attack and have it fire when the timer fully expires. From the client's perspective, this delay is no different than any other network latency.

Once the client receives the server confirmation, it can transition into the proper attack animation. The other clients likewise play the attack. The server indicates the time that the attack should have started; using this and the client's estimated latency, the clients can compress or lengthen the pre-attack/transition animation so that all the clients play the actual attack animation at roughly the same time that the attack happens on the server.

If the server sends back a MSG_ID_NO_WAY_JOSE message then you can instead transition from the pre-attack animation to an attack failed animation of some kind (using whatever in-game fiction you want to illustrate the failure). Using the above tricks, though, these failures should be extremely rare (to the point that they are probably only happening on clients with very low framerates or which have been hacked in an attempt to subvert cooldowns).

A lot of this also somewhat depends on the style of game. What is going to work best in an FPS is not the same as what works best in an RTS, for instance. Something at the pace of a typical MMORPG should work with what I've outlined above.

Exanple: client A presses the Lazer Beam Attack button. The game is dsigned for laser beam to have a ~0.4s warmup animation. Every client currently has a server latency of ~0.1s. The client sends the attack request to the server at t=0. The server receives it at t=0.1. The server confirms the attack and tells all the clients that client A started the attack at t=0 and the attack will go off at t=0.4. Client A receives this at t=0.2 so it knows that it has 0.2s left of the warmup animation to play. The other clients likewise just found out about the attack, so they know to play 0.2s of warmup animation for player A's avatar and then to play the attack animation.

Example 2: client A presses the Lazer Beam Attack button but due to client performance its timing is off and the client allows the player to press the button early. The server receives the request at t=0.1 and rejects it. The client receives the rejection at t=0.2, so the warmup animation has already been playing for 1/5th of a second. The client then transitions to a failure animation. None of the other clients ever know that anything happened.

Example 3: client A presses the Lazer Beam Attack button a hair earlier than allowed because of minute timing differences. The client sends the request at t=0 and the server receives it at t=0.1. The server knows that the client isn't allowed to attack until t=0.2. It waits until then and then tells all the clients that client A is attacking; the server offsets the time from the allowed time, not the received time, so it tells all clients that client A attacked at t=0.1 and the attack will go off at t=0.5. Client A receives this at t=0.3 and knows that it needs to length the warmup animation out a bit longer to t=0.5 (the animation normally takes 0.4s but in this case it needs to be slowed/extended to take a total of 0.5s). The other clients instead play the shortened warmup animation (because they only found out about it at t=0.3 and will do the full attack at t=0.5, for a duration of only 0.2s).

Note: these are not the only ways to solve these problems. There are other potential solutions, but they all involve some level of game design consideration and some amount of art support.

In short: you can't possibly force the client and server to be in sync, but you can use game design and art to hide the network latencies incurred by server-authoritative play.

\$\endgroup\$
2
\$\begingroup\$

With the following model the client will render only what the server tells him to render.
The client becomes an input & input-pre-emission-validator
The server is the final-validator (don't trust clients)
Then the client renders what the server told him

[1] Client action request creation upon user input :
Client clicks on an enemy, triggering your custom clickEvent(e)
obtaining the target_id & creating the following requestObject :
(assuming the action has been already client-validated [target is in sight, etc...])

{action:'attack', timestamp:e.timestamp, target:'target_id'}

[2] Client pre-emission verification :
Throttle & prevent spam of action_request to server checking the request :
requestObject.action & requestObject.timestamp

switch(request_action){
case 'attack':
    if(request_timestamp > localUser.attack.lastStamp + 1000){ // 1000 ms
        //emit request to the server
    }else{/*the request was too premature to be sent to the server */}
break; } 

[3] Server validation & response :
Upon reception, generate a receptionTimestamp and perform a check to see if the timestamp sent by the client is close enough (taking in account latency) to the one generated by the server.
Then verify that the client is allowed to perform the requested action,
and finally notify the clients involved in it.
(the server adds the resulting data after performing action calculations to the verified client_action to the object)

{action:'attack', target:'target_id', aggressor:'sender_id', result:'target_hit', damage:'damage_dealt'} //...etc...

[4] Clients receive the client_action from the Server :
Clients finally render the action, updating the game with the data contained in the client_action object sent previously by the server.

I hope it helps :]

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.