[Include] samp-account
#1

samp-account



samp-account was created to allow extensive user-account systems to be streamlined by not worrying about implementation details. This means we can have a fully working user account system, with data loaded from a database, and stored to a database, all with one function call.

samp-account uses the SA:MP native SQLite database system for storage, Slice’s pointers library for data binding, ******’ YSI hooks library for callback hooking, and ******' Whirlpool plugin for password hashing.

This is a repost of sampfw > account.inc. This is now a standalone library.

Installation

Simply install to your project:

Код:
sampctl package install bwhitmire55/samp-account
Include in your code and begin using the library:

Код:
#include <account>
Functions

Код:
/*
PARAMS:  
name - The name of the database column to store the data  
type - The psuedo-type of the data (TYPE_INT, TYPE_FLOAT, TYPE_STRING)  
{Float,_}:... - The variable to store the data  
  
RETURNS:  
1 on success, otherwise 0  
*/
stock AddAccountData(const name[ACCOUNT_MAX_COLUMN_NAME], Types: type, {Float,_}:...)
Код:
/*
PARAMS:  
playerid - The playerid attempting to be registered  
password - The password of the player (in plain text)  
  
RETURNS:  
1 on success, otherwise 0  
*/
stock RegisterPlayer(playerid, const password[])
Код:
/*
PARAMS:  
playerid - The playerid attempting to be logged in  
password - The password of the player (in plain text)  
  
RETURNS:  
1 on success, otherwise 0 
*/
stock LoginPlayer(playerid, const password[])
Код:
/*
PARAMS:  
playerid - The playerid to check  
  
RETURNS:  
1 (true) if logged-in, otherwise 0 (false) 
*/
bool: IsPlayerLoggedIn(playerid)
Код:
/*
PARAMS:  
playerid - The playerid to check  
  
RETURNS:  
The unique-ID of the player in the database if exists, otherwise 0  
*/
stock GetPlayerUID(playerid)
Usage

Simply create variables in which to store your players’ data

Код:
new
    gPlayerKills[MAX_PLAYERS],
    gPlayerHealth[MAX_PLAYERS],
    gPlayerNickname[MAX_PLAYERS][MAX_PLAYER_NAME];
Add that data to the system (and database) via AddAccountData

Код:
public OnGameModeInit() {
    AddAccountData("kills", TYPE_INT, gPlayerKills);
    AddAccountData("health", TYPE_FLOAT, gPlayerHealth);
    AddAccountData("nickname", TYPE_STRING, gPlayerNickname);

    return 1;
}
Anytime a user logs into their account, their information will be loaded from the database and into the corresponding variables. Likewise for disconnecting, their data will be updated inside the database.

NOTE: The variables do no reset themselves, so you should zero-out their values upon the player exiting the server.

You are free to use the variables as normal with no effect:

Код:
public OnPlayerDeath(playerid, killerid, reason) {
    gPlayerDeaths[playerid]++;
    return 1;
}
Now we just need to call RegisterPlayer or LoginPlayer. This will most likely be done via command/dialog

Код:
ZCMD:register(playerid, params[]) {
    if(IsPlayerLoggedIn(playerid)) {
        return SendClientMessage(playerid, 0xFF0000FF, "Already logged-in!");
    }

    if(isnull(params)) {
        return SendClientMessage(playerid, 0xFFFF00FF, "Usage: /register <password>");
    }

    if(RegisterPlayer(playerid, params)) {
        SendClientMessage(playerid, 0x00FF00FF, "You have successfully registered an account!");
    } else {
        // RegisterPlayer will return 0 if the account already exists, or there is an issue with the database.
        // For this example, we'll assume the former.
        SendClientMessage(playerid, 0xFF0000FF, "Error! This username is already registered.");
    }
    return 1;
}
And that’s it! You now have a fully working account system, which can store any data you like, without touching a database or file. Nice!

Callbacks

This library also includes two callbacks, OnPlayerRegister and OnPlayerLogin.

Код:
public OnPlayerRegister(playerid) {
    SendClientMessageToAll(0x00FF00FF, "A new member has registered!");
    return 1;
}

public OnPlayerLogin(playerid) {
    SendClientMessageToAll(0x00FF00FF, "An existing member has rejoined us!");
    return 1;
}
Macros

All macros are as followed:

Код:
// The database file
#define ACCOUNT_DATABASE        "mydatabase.db"
Код:
// The database table to store the account data
#define ACCOUNT_DATABASE_TABLE  "mydatabasetable"
Код:
// The amount of 'data' you wish to store
// i.e., how many times you will use AddAccountData
#define ACCOUNT_MAX_COLUMNS     (100)
Код:
// The maximum length of a column name in the database
#define ACCOUNT_MAX_COLUMN_NAME (24)
All of these can be redefined to suite your script

Код:
#define ACCOUNT_DATABASE        "COD-DB.db"
#define ACCOUNT_DATABASE_TABLE  "users"
#include <account>
Improvements / Updates

In the future, I may extend this to optionally include predefined commands or dialogs for the user accounts. Based on reception, I may also have an option to save to a MySQL database via plugin, or files for very simple accounts.

Testing

To test, simply run the package:

Код:
sampctl package run
Reply
#2

While the concept is very good and it reminds me of an ORM system, it has two major problems.

1) SQL Injection (Always escape any input by people. There is %q placeholder in `format` function)
2) Plain text passwords (Always hash + salt passwords)
Reply
#3

Glad you like it. I forgot to mention this uses Whirlpool internally for hashing.

Escaping was a complete oversight on my end. Will have it updated shortly!
Reply
#4

Why not use SA-MP's native hash function (SHA256_PassHash)? Makes it for one less dependency and it has salting within the function.
Reply
#5

Dunno but.. I'm not sure if it's useful? And limiting it to SQLite makes it a bit unnecessary.
Reply
#6

pawn Код:
enum PlayerData {
    pAdmin,
    pHide,
    pKills,
    pDeaths
}

new pInfo[MAX_PLAYERS][PlayerData];
If i were to guess i would say that wont work, but these is why im asking (so that i know for sure and not just guess).

While these should:
pawn Код:
new pInfo[PlayerData][MAX_PLAYERS];
Right?
Reply
#7

^ Uh, what?
Reply
#8

Quote:
Originally Posted by DRIFT_HUNTER
Посмотреть сообщение
pawn Код:
enum PlayerData {
    pAdmin,
    pHide,
    pKills,
    pDeaths
}

new pInfo[MAX_PLAYERS][PlayerData];
If i were to guess i would say that wont work, but these is why im asking (so that i know for sure and not just guess).

While these should:
pawn Код:
new pInfo[PlayerData][MAX_PLAYERS];
Right?
Both are correct. The usage depends on the declaration.
In the example, you will use the variable like that: "pInfo[playerid][pAdmin]" but in your way it's: "pInfo[pAdmin][playerid]"
I admit that the first way is clearer than urs.
Reply
#9

I really like the idea and i'd love to see some benchmarks, i know it's going to be slower than direct SQL queries but i want to see how much slower.

And if it performs out pretty well, you could implement a MySQL version of this and even take this further and create something like my EasyDB tried to achieve, but this would be way better approach!
Reply
#10

This has been updated to escape values retrieved via pointer by SaveAccountData. Note that the plain text from RegisterPlayer and LoginPlayer are left alone. This is as these values are hashed via Whirlpool prior to be executed.

Quote:
Originally Posted by DRIFT_HUNTER
Посмотреть сообщение
pawn Код:
enum PlayerData {
    pAdmin,
    pHide,
    pKills,
    pDeaths
}

new pInfo[MAX_PLAYERS][PlayerData];
If i were to guess i would say that wont work, but these is why im asking (so that i know for sure and not just guess).

While these should:
pawn Код:
new pInfo[PlayerData][MAX_PLAYERS];
Right?
I haven't really tested this with enums. Most modes written now days are done so in modules which removes the need for monstrous "player enums". My guess would be the second version should work.

Quote:
Originally Posted by Gammix
Посмотреть сообщение
I really like the idea and i'd love to see some benchmarks, i know it's going to be slower than direct SQL queries but i want to see how much slower.

And if it performs out pretty well, you could implement a MySQL version of this and even take this further and create something like my EasyDB tried to achieve, but this would be way better approach!
Thanks! I highly doubt there would be a mentionable difference in speed unless pointers.inc introduces something. If you take a look at the implementation of LoadAccountData and SaveAccountData, they are both essentially just query calls. They just build the query dynamically. Wouldn't hurt to run some tests though.
Reply
#11

I've done something similar to this that works with MySQL, but this is actually much, much simpler to use.
I will wait for an updated version which supports MySQL and uses BCrypt.
Reply
#12

I've actually made something very similiar to this a while ago for a modular gamemode I'm working on for MySQL, although your implementation is definitely better than mine so I might use yours instead and fork it, make it work with MySQL and BCrypt so I can use this instead.
Reply
#13

Quote:
Originally Posted by corne
Посмотреть сообщение
I've actually made something very similiar to this a while ago for a modular gamemode I'm working on for MySQL, although your implementation is definitely better than mine so I might use yours instead and fork it, make it work with MySQL and BCrypt so I can use this instead.
If you would like go ahead! I'm planning on updating this in the future but want to finish porting over other libraries from sampfw and completing a couple other projects first.
Reply
#14

Quote:
Originally Posted by nG Inverse
Посмотреть сообщение
If you would like go ahead! I'm planning on updating this in the future but want to finish porting over other libraries from sampfw and completing a couple other projects first.
I've started on this here last week, and have quite a bit done already, although this is for MariaDB and not MySQL, might build in MySQL compatibility in at a later point (which wouldn't be too hard, just uses an additional query). I'm currently dealing with a few bugs and want to make a few more improvements later on. Initially I wanted and actually started on adding MySQL support alongside the SQLite functionality, although decided not to because it would've become to messy when adding / changing some of the new things I added, although feel free to use any of my code if adding MySQL support later on.
Reply
#15

A minor update has been added to allow real-time saving of account data via UpdateAccountData.
pawn Код:
stock UpdateAccountData(playerid, {Float,_}:...)
The function can save a variable number of data up to the Pawn parameter limit; minus 1. So it can be called
pawn Код:
UpdateAccountData(playerid, gPlayerKills);
// or
UpdateAccountData(playerid, gPlayerKills, gPlayerScore);
A more complete example:
pawn Код:
new gPlayerKills[MAX_PLAYERS];
new gPlayerScore[MAX_PLAYERS];

public OnGameModeInit() {
    AddAccountData("kills", TYPE_INT, gPlayerKills);
    AddAccountData("score", TYPE_INT, gPlayerScore);
    return 1;
}

public OnPlayerDeath(playerid, killerid, reason) {
    if(IsPlayerLoggedIn(killerid)) {
        gPlayerKills[killerid]++;
        gPlayerScore[killerid] += 500;
        UpdateAccountData(killerid, gPlayerKills, gPlayerScore);
    }
    return 1;
}
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)