OnPlayerKeyStateChange

From SA-MP Wiki

Jump to: navigation, search


Description:

This callback is called when the state of any supported key is changed (pressed/released). Directional keys do not trigger OnPlayerKeyStateChange (up/down/left/right).


Parameters:
(playerid, newkeys, oldkeys)
playeridThe ID of the player that pressed or released a key.
newkeysA map (bitmask) of the keys currently held - see here.
oldkeysA map (bitmask) of the keys held prior to the current change - see here.


Return Values:

  • 1 - Allow this callback to be called in other scripts.
  • 0 - Callback will not be called in other scripts.
  • It is always called first in gamemodes so returning 0 there stops it from being called in filterscripts.


Tip

Image:Light_bulb_icon.png

Directional keys do not trigger OnPlayerKeyStateChange (up/down/left/right). They can only be detected with GetPlayerKeys (in OnPlayerUpdate or a timer).


Contents

Introduction

This callback is called whenever a player presses or releases one of the supported keys (see Keys). The keys which are supported are not actual keyboard keys, but San Andreas mapped function keys, this means that, for example, you can't detect when someone presses the spacebar, but can detect when they press their sprint key (which may, or may not, be assigned to the spacebar (it is by default)).

Parameters

The parameters to this function are a list of all keys currently being held down and all the keys held down a moment ago. The callback is called when a key state changes (that is, when a key is either pressed or released) and passes the states or all keys before and after this change. This information can be used to see exactly what happened but the variables can not be used directly in the same way as parameters to other functions. To reduce the number of variables only a single BIT is used to represent a key, this means that one variable may contain multiple keys at once and simply comparing values will not always work.

How NOT to check for a key

Let's presume that you want to detect when a player presses their FIRE button, the obvious code would be:

if (newkeys == KEY_FIRE)

This code may even work in your testing, but it is wrong and your testing is insufficient. Try crouching and pressing fire - your code will instantly stop working. Why? Because "newkeys" is no longer the same as "KEY_FIRE", it is the same as "KEY_FIRE" COMBINED WITH "KEY_CROUCH".

How to check for a key

So, if the variable can contain multiple keys at once, how do you check for just a single one? The answer is bit masking. Each key has its own bit in the variable (some keys have the same bit, but they are onfoot/incar keys, so can never be pressed at the same time anyway) and you need to check for just that single bit:

if (newkeys & KEY_FIRE)

Note that the single "&" is correct - this is a bitwise AND, not a logical AND, which is what the two ampersands are called.

Now if you test this code it will work whether you are crouching or standing when you press the fire key. However there is still one slight problem - it will fire as long as you are holding the key. OnPlayerKeyStateChange is called every time a key changes and that code is true whenever the the fire key is held down. If you press fire the code will fire, if that key is held and you press crouch - that code will fire again because a key (crouch) has changed and fire is still held down How do you detect when a key is first pressed, but not trigger again when it's still held and another key changes?

How to check for a key that has been pressed

This is where "oldkeys" comes in. To check if a key has just been pressed you need to first check whether it is set in "newkeys" - meaning it's held down, and then check that it's NOT in "oldkeys" - meaning it's only just been held down. The following code does this:

if ((newkeys & KEY_FIRE) && !(oldkeys & KEY_FIRE))

That will ONLY be true when the FIRE key is first pressed, not when it's held and another key changes.

How to check for a key being released

Exactly the same principle as above, but reversed:

if ((oldkeys & KEY_FIRE) && !(newkeys & KEY_FIRE))

How to check for multiple keys

If you want to check for players HOLDING crouch and fire then the following code will work fine:

if ((newkeys & KEY_FIRE) && (newkeys & KEY_CROUCH))

However if you want to detect when they FIRST press fire and crouch the following code WILL NOT work. It will work if they manage to press the two keys at exactly the same time, but if they're fractionally out (far less than half a second) it won't:

if ((newkeys & KEY_FIRE) && !(oldkeys & KEY_FIRE) && (newkeys & KEY_CROUCH) && !(oldkeys & KEY_CROUCH))

Why not? Because OnPlayerKeyStateChange is called every time a single key changes. So they press "KEY_FIRE" - OnPlayerKeyStateChange is called with "KEY_FIRE" in "newkeys" and not in "oldkeys", then they press "KEY_CROUCH" - OnPlayerKeyStateChange is called with "KEY_CROUCH" and "KEY_FIRE" in "newkeys", but "KEY_FIRE" is now also in "oldkeys" as it's already been pressed, so "!(oldkeys & KEY_FIRE)" will fail. Fortunately the solution is very simple (in fact simpler than the original code):

if ((newkeys & (KEY_FIRE | KEY_CROUCH)) == (KEY_FIRE | KEY_CROUCH) && (oldkeys & (KEY_FIRE | KEY_CROUCH)) != (KEY_FIRE | KEY_CROUCH))

This may look complicated, but it checks that both keys are set in "newkeys" and that both the keys were not set in "oldkeys", if one of them was set in "oldkeys" that doesn't matter as not both of them were. All these things can be simplified greatly with defines.

Simplification

Detecting holding a key

The define:

// HOLDING(keys)
#define HOLDING(%0) \
	((newkeys & (%0)) == (%0))

Holding one key:

if (HOLDING( KEY_FIRE ))

Holding multiple keys:

if (HOLDING( KEY_FIRE | KEY_CROUCH ))

Detecting first pressing a key

The define:

// PRESSED(keys)
#define PRESSED(%0) \
	(((newkeys & (%0)) == (%0)) && ((oldkeys & (%0)) != (%0)))

Pressed one key:

if (PRESSED( KEY_FIRE ))

Pressed multiple keys:

if (PRESSED( KEY_FIRE | KEY_CROUCH ))

Detecting if a player is pressing a key currently

The define:

// PRESSING(keyVariable, keys)
#define PRESSING(%0,%1) \
	(%0 & (%1))

Pressing one key:

if (PRESSING( newkeys, KEY_FIRE ))

Pressing multiple keys:

if (PRESSING( newkeys, KEY_FIRE | KEY_CROUCH ))

Detecting releasing a key

The define:

// RELEASED(keys)
#define RELEASED(%0) \
	(((newkeys & (%0)) != (%0)) && ((oldkeys & (%0)) == (%0)))

Released one key:

if (RELEASED( KEY_FIRE ))

Released multiple keys:

if (RELEASED( KEY_FIRE | KEY_CROUCH ))

Examples

Attach NOS when the player presses fire

public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
	if (PRESSED(KEY_FIRE))
	{
		if (IsPlayerInAnyVehicle(playerid))
		{
			AddVehicleComponent(GetPlayerVehicleID(playerid), 1010);
		}
	}
	return 1;
}

Super jump

public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
	if (PRESSED(KEY_JUMP))
	{
		new
			Float:x,
			Float:y,
			Float:z;
		GetPlayerPos(playerid, x, y, z);
		SetPlayerPos(playerid, x, y, z + 10.0);
	}
	return 1;
}

God mode while holding use

new
	Float:gPlayerHealth[MAX_PLAYERS];
 
#if !defined INFINITY
	#define INFINITY (Float:0x7F800000)
#endif
 
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
	if (PRESSED(KEY_ACTION))
	{
		// They just pressed the action key, save their
		// old health for restoration.
		GetPlayerHealth(playerid, gPlayerHealth[playerid]);
		SetPlayerHealth(playerid, INFINITY);
	}
	else if (RELEASED(KEY_ACTION))
	{
		// They just let go of action - restore
		// their old health again.
		SetPlayerHealth(playerid, gPlayerHealth[playerid]);
	}
	return 1;
}

Explanation

You don't need to worry about HOW it's done, just that it is. HOLDING detects if they're pressing a key (or keys), regardless of wether they were pressing it before, PRESSED detects if they only just pressed the key(s) and RELEASED detects if they just released a key(s). However if you want to know more - read on.

The reason why you need to do it this way, not just using & or ==, is to detect exactly the keys you want while ignoring others which may or may not be pressed. In binary KEY_SPRINT is:

0b00001000

and KEY_JUMP is:

0b00100000

thus ORing them into the wanted keys (we could also add them in this example but that's not always the case) gives:

0b00101000

If we were only using & and OnPlayerKeyStateChange was called for a player pressing jump we would get the following code:

newkeys = 0b00100000
wanted  = 0b00101000
ANDed   = 0b00100000

The AND of the two numbers is not 0, thus the result of the check is true, which isn't what we want.

If we only used == the two numbers are clearly not the same thus the check would fail, which is what we want.

If the player was pressing jump, sprint and crouch we would get the following code:

newkeys = 0b00101010
wanted  = 0b00101000
ANDed   = 0b00101000

The ANDed version is the same as the required keys and also not 0, thus will give the correct answer, however the two original numbers are not the same so == will fail. In both the examples one of the two has given the right answer and one has given the wrong answer. If we compare the first one using & and == we get:

newkeys = 0b00100000
wanted  = 0b00101000
ANDed   = 0b00100000

Obviously wanted and ANDed are not the same so the check fails, which is correct. For the second example:

newkeys = 0b00101010
wanted  = 0b00101000
ANDed   = 0b00101000

Wanted and ANDed are the same so comparing them as equal will result in a true result, which again is correct.

So using this method we can accurately check if certain keys are pressed ignoring all other keys which may or may not be pressed. the oldkeys check just uses != instead of == to ensure that the required keys were not previously pressed, so we know one of them was just pressed.

Related Functions

The following functions might be useful, as they're related to this callback in one way or another.

Personal tools
In other languages