Admin panel
From SA-MP Wiki
This article is outdated. It may use methods or functions which no longer exist or which are deemed obsolete by the community. Caution is advised. |
Contents |
Before we start the script itself
An Admin Script or parts of it (like login script) is what people are often requesting in #sa-mp.scripting, so I'll post some ideas here. This tutorial hopefully requires some experience you may got by trying around with Pawn and reading and learning with other Pawn Tutorials.
So, at first, we start pawno and delete everything what's in the script right now and add at our script some simple
#include <a_samp> #include <dini>
This includes the SA:MP library with most native functions, although you can not be sure of this being correctly, and it uses dini (easier access to manage user files). To work in future with everything working with floats, go to your SA/samp/pawno/includes/ directory and edit float.inc. Look for native Float:strfloat(const string[]); and replace it with native Float:floatstr(const string[]); - (not needed for the last versions of SA:MP) - as this has been wrongfully named in the 0.1's float.inc and is else going to output you some run time error/function not found. But in case you share your script with people who didn't change it, there's a way to get this working too:
#if !defined floatstr native Float:floatstr(const string[]); If the user is logged in.
If you have a not-500-player server running, there's no need to use MAX_PLAYERS as 500 - it would on a 8 player server run loops (later done) 492 times too often and would too large variables. So, if it is a 8-Player-Server, we can simply set the SLOTS to 8. (This is done when you compile, so if anyone other compiles this, he may needs to change this)
#define SLOTS 8
And we can remove the MAX_PLAYERS:
#undef MAX_PLAYERS
Login
If you want to see if the user is logged in, you need to store this Information in some global variable. Global variables are not set within a function, they're usually defined at the top of the script - after the part we did already and before some functions are there.
new player_level[SLOTS] = {-1, ...}; // Saves the player's level - the default // one is -1 for not connected, 0 for not logged in and others for logged in // All elements are set to -1 at start new player_names[SLOTS][MAX_PLAYER_NAME]; // Saves the player's names
This variable tracks every players level and the other ones every players name. As level we use currently -1 - not connected, 0 - not logged in and 1 - logged in. This script uses it's own functions to make it working for admins.
#define USERFILE "users.txt"
This just defines the name of the file where all user data is stored (name and password)
The command itself is a bit more complicated now. So, we're going this through step-by-step:
At the top:
#define dcmd(%1,%2,%3) if ((strcmp(%3, "/%1", true, %2+1) == 0)&&(((%3[%2+1]==0)&&(dcmd_%1(playerid,"")))||((%3[%2+1]==32)&&(dcmd_%1(playerid,%3[%2+2]))))) return 1
This helps us with 2 things: Firstly, our script is a way faster than by large strcmp() and strtok things. And we have a nice functions like dcmd_login called when a user types /login.
public OnPlayerCommandText(playerid, '''cmdtext[]''') { dcmd(login,5,cmdtext); // dcmd('''command-name''' (without /), '''length of the command''', '''cmdtext'''); return 0; // This shows SERVER: Unkown Command if there is no command in this script for this, like /login }
If we try to compile now, the compiler outputs the following errors:
error 017: undefined symbol "dcmd_login" warning 203: symbol is never used: "player_level"
We see, the compiler tries to do it like if we type /login, it executes dcmd_login, but fails. And player_level is not used - so we do not save whenever the player is logged in or not.
dcmd_login(playerid, params[]) { if(player_level[playerid] != 0) { // User is logged in SendClientMessage(playerid, 0xFFFFFFFF, "You are already logged in."); return 1; // We dont need to execute the rest of the function, do we? } else if(strlen(params) == 0) { // There is no password specified, only /login SendClientMessage(playerid, 0xFFFFFFFF, "Please use /login [password]"); return 1; } else if(adler32_hash(params) != dini_Int(USERFILE,player_names[playerid])) { // There's a password '''hash''' generated and compared to the one from the config file SendClientMessage(playerid, 0xFFFFFFFF, "Password mismatch."); return 1; } player_level[playerid] = 1; // Logged in successfully SendClientMessage(playerid, 0xFFFFFFFF, "You are now logged in. Have a nice day."); return 1; }
To get this hash, we need to calculate it, I do it here with adler32. Doing it in PAWN:
adler32_hash(buf[]) { new length=strlen(buf); new s1 = 1; new s2 = 0; new n; for (n=0; n<length; n++) { s1 = (s1 + buf[n]) % 65521; s2 = (s2 + s1) % 65521; } return (s2 << 16) + s1; }
A few things are missing:
- the player name isn't set, so this is never working
- if you quit, new players with the same id are logged in.
We can avoid this by adding 2 other callbacks:
public OnPlayerConnect(playerid) { if(player_level[playerid] == -1) { // This check is done, because if a game mode changes, OnPlayerConnect is called for everyone connected and would reset all levels. player_level[playerid] = 0; // 0 is set -> player is connected, but not logged in. GetPlayerName(playerid,player_names[playerid],MAX_PLAYER_NAME); // Getting the playername and saving it } return 1; } public OnPlayerDisconnect(playerid) { player_level[playerid] = -1; // the -1 is set -> user isn't logged in anymore return 1; }
My Script looks now like this.
Comparison with RCON
RCON without Scripts | RCON with Scripts | own scripts | |
---|---|---|---|
multiple users with different passwords | No | No | Yes |
different levels | No | Partial (has to be preconfigured, based on name and can be avoided) | Yes |
Kick, Ban | Yes | Yes | Yes |
showing messages to players | Yes | Yes | Yes |
denying commands on "higher" users | No | No (/rcon kick is always executeable, while /kick may not take effect) | Yes |