18.10.2010, 07:34
(
Last edited by Slice; 02/02/2011 at 06:35 AM.
)
Tambiйn estб disponible en espaсol.
Muchas gracias a MrDeath.
hey,
I've noticed a lot of bugs and problems people have with scripting is caused by lack of knowledge in this area. After reading this, I hope I'm going to have cleared a few things up.
Topics:
This might sound complicated for some people, allow me to explain: All the actions the server does are always after waiting for the latest action to finish.
For example, if you have code that takes 3 seconds to run in OnPlayerConnect (such as the old GeoIP plugin), then the server will wait for that code to finish before doing anything else.
Things that might take a lot of time:
Benefits
The benefits of having the server run on a single thread (in my opinion) is you have full control over the flow of information to the server; you will always process every action in the server as it comes in. If the server would be multi-threaded, then player information could get updated while your code is running. I don't think that would be any better, honestly.
Player updates
When any of these things changes for the client, the client will send information to the server and the server will call OnPlayerUpdate:
What happens when a player moves the camera, starts moving, and presses the action key after that?
1. If the player was aiming/shooting, the camera angle and facing angle will be sent in the same update and OnPlayerUpdate will be called. If the player wasn't aiming/shooting, OnPlayerUpdate might or might not be called at this moment (probably not).
2. OnPlayerUpdate is called for the new key state; OnPlayerKeyStateChange is not called because the key is left/right or up/down. The player's animation will also be updated before OnPlayerUpdate is called, if it was changed instantly.
Note: I'm not 100% sure if the animation will be in the same update as the keys, I can't test that at the moment.
The ID returned from GetPlayerAnimationIndex changes as fast as a new animation is initialized for the client. For example, when you start running the animation will be changed the moment you press a key to move (even though you might not notice it instantly).
What happens when I return 0 in OnPlayerUpdate?
OnPlayerUpdate is called before the server sends the new information to the other players. If you return 0 in any OnPlayerUpdate call (filterscripts or gamemode), the update will not be sent to other clients.
Example of good usage
This will stop the player from shooting or moving/warping back when froze. No more "ghost shooting" or whatever you might call it!
Example of bad usage
Below, return 1 is not in the function so every single player update won't be synced.
Better code:
Player-affecting server functions
Functions such as SetPlayerHealth, GivePlayerMoney, PutPlayerInVehicle, etc. all send a message from the server only to the player it is affecting (SetPlayerVirtualWorld is an exception here).
What does that mean?
Well.. If the player has 73 health and you use SetPlayerHealth to set it to 100, this is what happens:
SA-MP is synced by copying the keys you press and applying them for all other clients. If you press aim and fire, you will press aim and fire for other clients aswell; that means, even if you're shooting on your screen you might be standing in front of a desynced vehicle or be knifed/dead from a car explosion on other player's screens. This is referred to as desync.
Fixing a desynced player
To fix a desynced player, the only proper way is to stream the player in/out or respawn the player. Streaming the player in/out would take about a second; therefore, respawning the player is the most essential solution.
I'll get back on this with fully working re-sync code!
Keys sent to the server
Some might believe, if you press and release a button real quick the server won't see it. Wrong! For example, if you press and release fire the server will receive 2 updates: you pressing fire, and you releasing fire.
Packet loss
Sometimes, when a player update is sent to the server, it might get lost out in the big world. This might cause bugs or problems in the script; be prepared for packet loss!
How can I avoid packet-loss related script bugs?
Never trust the client, both on good and evil. You never know if the client is a script kiddie trying to mess with your server, someone with a bad wifi/connection, or someone with a random packet loss.
Packet loss usually causes confusion with player information, for example:
Packet misorder is when messages sent doesn't get received in the order they were sent.
A scenario:
I hope you learned something from this tutorial. If I provided faulty/diffuse information, let me know! I might add more information to this topic.
Muchas gracias a MrDeath.
hey,
I've noticed a lot of bugs and problems people have with scripting is caused by lack of knowledge in this area. After reading this, I hope I'm going to have cleared a few things up.
Topics:
- The script runs on a single thread
- Player updates
- Player-affecting server functions
- Key sync
- Packet loss (advanced)
- Packet misorder (advanced)
This might sound complicated for some people, allow me to explain: All the actions the server does are always after waiting for the latest action to finish.
For example, if you have code that takes 3 seconds to run in OnPlayerConnect (such as the old GeoIP plugin), then the server will wait for that code to finish before doing anything else.
Things that might take a lot of time:
- Dealing with large strings
- Looping through players, doing several things
- Reading and writing a lot to files
Benefits
The benefits of having the server run on a single thread (in my opinion) is you have full control over the flow of information to the server; you will always process every action in the server as it comes in. If the server would be multi-threaded, then player information could get updated while your code is running. I don't think that would be any better, honestly.
Player updates
When any of these things changes for the client, the client will send information to the server and the server will call OnPlayerUpdate:
- Health/armour
- Vehicle health, body damage, color, mods
- Death
- Velocity
- Position/rotation
- Animation
- Pressed keys
- Weapon/ammo
- Camera position (only when aiming/shooting; if not, it's sent about twice a second)
What happens when a player moves the camera, starts moving, and presses the action key after that?
1. If the player was aiming/shooting, the camera angle and facing angle will be sent in the same update and OnPlayerUpdate will be called. If the player wasn't aiming/shooting, OnPlayerUpdate might or might not be called at this moment (probably not).
2. OnPlayerUpdate is called for the new key state; OnPlayerKeyStateChange is not called because the key is left/right or up/down. The player's animation will also be updated before OnPlayerUpdate is called, if it was changed instantly.
Note: I'm not 100% sure if the animation will be in the same update as the keys, I can't test that at the moment.
The ID returned from GetPlayerAnimationIndex changes as fast as a new animation is initialized for the client. For example, when you start running the animation will be changed the moment you press a key to move (even though you might not notice it instantly).
What happens when I return 0 in OnPlayerUpdate?
OnPlayerUpdate is called before the server sends the new information to the other players. If you return 0 in any OnPlayerUpdate call (filterscripts or gamemode), the update will not be sent to other clients.
Example of good usage
This will stop the player from shooting or moving/warping back when froze. No more "ghost shooting" or whatever you might call it!
pawn Code:
new
bool:g_IsPlayerFroze[ MAX_PLAYERS ] // Store a variable for each player defining wether they have been froze or not.
;
public OnPlayerSpawn( playerid )
{
g_IsPlayerFroze[ playerid ] = false; // The player always gets unfroze upon spawning
}
public OnPlayerUpdate( playerid )
{
static // I use static variables here because they won't have to get initialized each time the function is executed.
s_Keys,
s_UpDown,
s_LeftRight
;
GetPlayerKeys( playerid, s_Keys, s_UpDown, s_LeftRight ); // Get the keys currently being pressed
if ( g_IsPlayerFroze[ playerid ] && ( s_Keys || s_UpDown || s_LeftRight ) ) // If any keys are pressed, don't sync the update
return 0;
return 1;
}
// Use the following 2 functions to freeze/unfreeze players
stock FreezePlayer( playerid )
{
g_IsPlayerFroze[ playerid ] = true; // The server now treats the player as a froze player
TogglePlayerControllable( playerid, false );
}
stock UnfreezePlayer( playerid )
{
g_IsPlayerFroze[ playerid ] = false; // Opposite of the comment above!
TogglePlayerControllable( playerid, true );
}
Below, return 1 is not in the function so every single player update won't be synced.
pawn Code:
public OnPlayerUpdate( playerid )
{
if ( GetPlayerWeapon( playerid ) == WEAPON_MINIGUN )
Ban( playerid );
}
pawn Code:
public OnPlayerUpdate( playerid )
{
if ( GetPlayerWeapon( playerid ) == WEAPON_MINIGUN )
Ban( playerid );
return 1;
}
Functions such as SetPlayerHealth, GivePlayerMoney, PutPlayerInVehicle, etc. all send a message from the server only to the player it is affecting (SetPlayerVirtualWorld is an exception here).
What does that mean?
Well.. If the player has 73 health and you use SetPlayerHealth to set it to 100, this is what happens:
- SetPlayerHealth( playerid, 100.0 ) -> The server sends a message to the affected client telling it to update the health to 100.
- The client is currently paused so the message is put in a pool and will be handled once the player is unpaused and all the messages in the pool before it was handled.
- A timer in the gamemode uses GetPlayerHealth on the player and it shows 73.
- The client now unpaused, and all the messages in the pool are now handled.
- The health is changed for the client.
- An update it sent to the server.
- OnPlayerUpdate is called.
- The new health is sent to all other players on the server, and they now see the player having health 100!
SA-MP is synced by copying the keys you press and applying them for all other clients. If you press aim and fire, you will press aim and fire for other clients aswell; that means, even if you're shooting on your screen you might be standing in front of a desynced vehicle or be knifed/dead from a car explosion on other player's screens. This is referred to as desync.
Fixing a desynced player
To fix a desynced player, the only proper way is to stream the player in/out or respawn the player. Streaming the player in/out would take about a second; therefore, respawning the player is the most essential solution.
I'll get back on this with fully working re-sync code!
Keys sent to the server
Some might believe, if you press and release a button real quick the server won't see it. Wrong! For example, if you press and release fire the server will receive 2 updates: you pressing fire, and you releasing fire.
Packet loss
Sometimes, when a player update is sent to the server, it might get lost out in the big world. This might cause bugs or problems in the script; be prepared for packet loss!
How can I avoid packet-loss related script bugs?
Never trust the client, both on good and evil. You never know if the client is a script kiddie trying to mess with your server, someone with a bad wifi/connection, or someone with a random packet loss.
Packet loss usually causes confusion with player information, for example:
- Player 1 has 50 armour.
- The server has a brand new, top-notch anti-armour cheat based on player updates!
- The anti-armour cheat will detect if the armour changed, and the player didn't lose $240 while in an ammu-nation.
- Player 1 goes into an ammo. The server now knows that, because of code in OnPlayerInteriorChange.
- Player 1 buys an armour and sends an update to the server with the new armour and pays $240.
- Player 1 sends a packet to the server with that information; the packet is lost!
- Player 1 starts moving around, updates are sent for this but the server has no idea about the $240 or the armour.
- Player 1 gets shot, the new armour is sent in a player update to the server. The server still has no idea about the $240!
- The server anti-cheat sees this, and bans the player for armour cheating.
- Notifying admins and adding what happened to a hidden "suspected cheating count" (that would ban after for example 3 suspicious events).
- Giving the player $1 and wait for the new money update; possibly remove the armour and give it back only if new money is received within the next 1-3 player updates.
Packet misorder is when messages sent doesn't get received in the order they were sent.
A scenario:
- Player 1 has 85 armour.
- The server has a brand new, top-notch anti-armour cheat based on player updates!
- The anti-armour cheat will detect if the armour changed, and the player didn't lose $240 while in an ammu-nation.
- Player 1 is fighting for his life!
- Player 1 gets hit twice, first on 10 hp then 20 hp; two packets with new armour are sent to the server.
- The packets are misplaced; the server first receives a message telling the player has 55 armour (85-10-20).
- The first package gets to the server after the second, with armour 75.
- The server thinks the player's armour has increased and bans the player for armour cheating.
- Notifying admins and adding what happened to a hidden "suspected cheating count" (that would ban after for example 3 suspicious events).
- Not take any actions until 2-3 more player updates; still only generate a warning.
- Take actions only if the new armour is larger than or equal to 100.
I hope you learned something from this tutorial. If I provided faulty/diffuse information, let me know! I might add more information to this topic.