Fast Commands
From SA-MP
Contents |
Introduction
By far the most common command processing system in terms of use is the strtok method first used for SA:MP in the original LVDM by jax. This exposure from the very start has firmly established it's use in scripts, however it is very slow in comparison to other methods, one of these methods, dcmd, is explained here along with a method of extracting parameters safely and quickly from the entered command.
dcmd
Comparison example
Using strtok:
public OnPlayerCommandText(playerid, cmdtext[]) { new index, cmd[20]; cmd = strtok(cmdtext, index); if (strcmp(cmd, "/heal", true) == 0) { new tmp[20], id; tmp = strtok(cmdtext, index); if (strlen(tmp)) { id = strval(tmp); if (IsPlayerConnected(id)) { SetPlayerHealth(id, 100.0); SendClientMessage(id, 0x00FF00AA, "You have been healed"); SendClientMessage(playerid, 0x00FF00AA, "Player healed"); } else { SendClientMessage(playerid, 0xFF0000AA, "Player not found"); } } else { SendClientMessage(playerid, 0xFF0000AA, "Usage: \"/heal <playerid>\""); } return 1; } return 0; }
Using dcmd:
public OnPlayerCommandText(playerid, cmdtext[]) { dcmd(heal, 4, cmdtext); return 0; } dcmd_heal(playerid, params[]) { if (strlen(params)) { id = strval(params); if (IsPlayerConnected(id)) { SetPlayerHealth(id, 100.0); SendClientMessage(id, 0x00FF00AA, "You have been healed"); SendClientMessage(playerid, 0x00FF00AA, "Player healed"); } else { SendClientMessage(playerid, 0xFF0000AA, "Player not found"); } } else { SendClientMessage(playerid, 0xFF0000AA, "Usage: \"/heal <playerid>\""); } return 1; }
There is already less code and we've barely done anything, converting the first example into the second took less than 30 seconds and will save a lot of time in processing. Plus the command being in a separate function means you can put it in a separate file if you want and just have a list of commands in OnPlayerCommandText, not a huge load of code.
Explanation
dcmd(heal, 4, cmdtext);
That line basically declares the command, it checks the variable cmdtext to see if you typed /heal, the number 4 is the length of the word "heal", the word "heal" (without double quotes) is both the command to type and part of the function to call.
The function dcmd_heal is the command name you entered in the dcmd line with dcmd_ put in front. It has two parameters, the first, playerid, is the player who typed the command, just like OnPlayerCommandText. The second, params[], is the string entered after the command, this is very similar to cmdtext[] but ONLY contains command parameters (or nothing if no parameters were entered, as shown in the example above).
This is all you need to know to use dcmd, most people are put off by the last remaining part, however this was purposefully left to last as you do not need to understand it at all to use it.
Define line
This is the dcmd define line, just copy the following line to the top of your script, near the includes, and forget about it (Note: may scroll off to the right):
#define dcmd(%1,%2,%3) if (!strcmp((%3)[1], #%1, true, (%2)) && ((((%3)[(%2) + 1] == '\0') && (dcmd_%1(playerid, ""))) || (((%3)[(%2) + 1] == ' ') && (dcmd_%1(playerid, (%3)[(%2) + 2]))))) return 1
sscanf
example revisited
The above example can be simplified even further by the use of sscanf:
public OnPlayerCommandText(playerid, cmdtext[]) { dcmd(heal, 4, cmdtext); return 0; } dcmd_heal(playerid, params[]) { new id; if (sscanf(params, "d", id)) SendClientMessage(playerid, 0xFF0000AA, "Usage: \"/heal <playerid>\""); else if (!IsPlayerConnected(id)) SendClientMessage(playerid, 0xFF0000AA, "Player not found"); else { SetPlayerHealth(id, 100.0); SendClientMessage(id, 0x00FF00AA, "You have been healed"); SendClientMessage(playerid, 0x00FF00AA, "Player healed"); } return 1; }
Explanation
sscanf takes two main parameters, the string you want to get information from (in this case params) and the type of information you want to get out (see below). It then takes additional parameters, equal in number to the amount of data you want, in this example we only want one piece of data (the id of the player to heal) and it's an integer (either d or i, here using d), so we pass one parameter to get the id.
If sscanf gets all the data properly it returns 0, so we can check it's not 0 and if it is then it failed and the player typing the command didn't enter the data correctly.
Data types
The second parameter is a list of the types of data required. sscanf can get strings, numbers, characters, floats and hex numbers from a command (or any other string). For example:
/givecash <playerid> <amount>
That is two numbers (the playerid and the amount) so the type list would be "number number", numbers are represented by either "i" or "d" (both represent numbers in format and printf) so the format parameter for that command could be any of the following:
"ii" "dd" "id" "di"
Another common example is:
/me <action>
The action is just a string so the format parameter is simply "s" (there are no other options for strings).
An example of combining types could be:
/ban <playerid> <reason>
This is a number followed by a string so the format parameter is either "is" or "ds" (again, both d and i mean number).
| Format character | Data type | Example |
|---|---|---|
| d | Integer | Player ID |
| i | Integer | Player ID |
| c | Character | A single letter |
| s | String | Any length of text |
| h | Hex number | Colour |
| x | Hex number | Colour |
| f | Float | Co-ordinate |
| z | Optional string | Ban reason, only optional at the end of a format string |
Strings
Strings are handled slightly cleverly in sscanf. If the "s" is the last format parameter the string will contain everything remaining in the source string, if it isn't it will just get a single word similar to strtok:
sscanf("hello there you", "s", string);
string will be "hello there you"
sscanf("hello there you", "ss", string1, string2);
string1 will be "hello", string2 will be "there you" (neither string will contain the space between "hello" and "there").
sscanf("hello 0", "si", string, player);
string will contain "hello", player will be 0.
sscanf("hello player 0", "si", string, player);
This sscanf will actually fail as string will get "hello", but "player" isn't a number, which is the required parameter type.
If you want the last parameter to be a string but only want one word you can simply put a space after the "s" in the parameter string, this will trick sscanf into thinking there's more types wanted but it will just skip the space when it gets to it:
sscanf("hello there", "s ", string);
string will now only contain "hello".
Optional strings
The "z" format parameter when used at the end of a string will create an optional string parameter. It behaves exactly the same as the "s" parameter, especially when used in parts of the format string other than the end, but if the text passed is not long enough the sscanf function still returns 0 for true:
dcmd_ban(playerid, params[]) { new id, reason[64]; if (sscanf(params, "dz", id, reason)) SendClientMessage(playerid, 0xFF0000AA, "Usage: \"/ban <playerid> <reason (optional)>\""); else if (!IsPlayerConnected(id)) SendClientMessage(playerid, 0xFF0000AA, "Player not found"); else { BanEx(id, reason); format(reason, sizeof (reason), "You have been banned%s%s.", reason[0] ? (" for: ") : (""), reason); SendClientMessage(id, 0xFF0000AA, reason); SendClientMessage(playerid, 0x00FF00AA, "Player banned"); } return 1; }
Other notes
- sscanf has inbuilt protection against the long number crash (where very long numbers can crash strval).
- IsNumeric is included, sscanf won't try evaluate strings as numbers (which will just return 0) if they typed the command wrong.
- Simple to add more parameters to a command without major code restructuring.
Code
To use sscanf simply copy the code here somewhere in your mode.
givecash
The infamous givecash command from LVDM using dcmd and sscanf, this is obviously much shorter than normal implementations.
public OnPlayerCommandText(playerid, cmdtext[]) { dcmd(givecash, 8, cmdtext); return 0; } dcmd_givecash(playerid, params[]) { new giveplayerid, amount; if (sscanf(params, "dd", giveplayerid, amount)) SendClientMessage(playerid, 0xFF0000AA, "Usage: /givecash [playerid] [amount]"); else if (!IsPlayerConnected(giveplayerid)) SendClientMessage(playerid, 0xFF0000AA, "Player not found"); else if (amount > GetPlayerMoney(playerid)) SendClientMessage(playerid, 0xFF0000AA, "Insufficient Funds"); else { GivePlayerMoney(giveplayerid, amount); GivePlayerMoney(playerid, 0 - amount); SendClientMessage(playerid, 0x00FF00AA, "Money sent"); SendClientMessage(giveplayerid, 0x00FF00AA, "Money recieved"); } return 1; }
