(https://upload.wikimedia.org/wikipedia/en/thumb/6/62/MySQL.svg/640px-MySQL.svg.png)
Pozdrav,
Danas cu vam objasniti kako napraviti sistem registracije sa mysql pluginom.
Za one koji ne znaju, mysql je sistem za upravljanje bazom(bazama) podataka.
Na ovom forumu koliko ja znam jos uvek nema slicnog tutoriala a i "skripteri" nisu bas najbistriji pa cu se potruditi da vas bar nesto naucim. Tutorial sadrzi:
- Spajanje sa databazom.
- Konfigurisanje databaze.
- Unosenje podataka u tablicu.
- Ucitavanje podataka iz tablice.
- Spremanje podataka u tablicu.
Prva stvar, definisemo mysql handle, tj, o ovome mozete misliti kao "stvarcica" koja omogucuje izravan pristup bazi koji koristimo za pohranu podataka i slicno.
[pawn]new MySQL:handle;[/pawn]
Kada smo to dodali potrebno je da napravimo enumator koji sadrzi korisnicke informacije tipa SQLID koji sluzimo kasnije za pohranu podataka, updejt podataka i slicno.
NAPOMENA: SQLID
nije obavezan i nije opsta praksa, ali ukoliko imate dosta korisnika mysql ce raditi dosta brze sa pretragom preko integera nego brze (memory save).
[pawn]enum ENUM_PLAYER_DATA
{
SQLID, // sqlid, unikatni user id
Password[65], // password, za ovo koristimo SHA256 password hash koji je dodat u zadnjoj samp verziji
Salt[11], // nasumicno generisano da modifikuje originalni hash,
RegDate[65] // datum registracije, ovo je samo "test" da vam pokazem kako da uzmete nesto iz tablice
}
new PlayerInfo[MAX_PLAYERS][ENUM_PLAYER_DATA];[/pawn]
[pawn]#define DIALOG_LOGIN (0)
#define DIALOG_REGISTER (1)[/pawn]
Sledece, sada definisemo mysql informacije koje koristimo za konekciju sa databazom, ja cu ovde da stavim informacije sa localhosta, vi naravno izmenite po potrebi:
[pawn]#define MYSQL_HOST "localhost"
#define MYSQL_USER "root"
#define MYSQL_PASSWORD "" // po defaultu na localhostu nemate password, osim ukoliko ne koristite MAMP onda je password root
#define MYSQL_DATABASE "samp"[/pawn]
Sada kada smo napravili enumator i definisali dialoge i informacije za konekciju prelazimo na konekciju.
[pawn]public OnGameModeInit()
{
handle = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE); // ovde sada koristimo onaj vec definisani handle i pokusavamo da se konektujemo sa vec definisanim informacijama
if(mysql_errno(handle) != 0 || handle == MYSQL_INVALID_HANDLE) // ukoliko postoji eror kod konekcije sa databazom ispisujemo sledece i zatvaramo konzolu ->
{
print("Konekcija sa databazom nije uspesna, pogledajte da li su sve informacije ispravne.");
SendRconCommand("exit");
return true;
}
mysql_tquery(handle, "CREATE TABLE IF NOT EXISTS `players` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(24) NOT NULL , `password` VARCHAR(65) NOT NULL , `salt` VARCHAR(11) NOT NULL , `regdate` DATETIME NOT NULL , PRIMARY KEY (`id`))");
// Ovaj upit radi sledece:
// Proverava da li postoji tabela players i ukoliko ne postoji kreira istu
// id postavlja kao primary key i stavlja ga na auto increment sto znaci da prilikom svakog unosa u bazu id ce se sam podizati za jedan.
// name postavlja kao string duzine 24 karaktera, mozete name staviti da bude i unique sto bi znacilo da je to ime jedinstveno i da se ne moze uneti drugi red sa istim imenom, ali meni je to nepotrebno zbog provere na konektu.
// password stavljamo kao string isto duzine 65 karaktera
// salt isto samo sto je duzina 11 karaktera
// i na kraju regdate je datum registracije njega postavljamo kao DATETIME sto je u sustine vreme i datum.
return true;
}[/pawn]
Ukoliko vam je ovo zadnje ipak zbunjujuce mozete i rucno ovo uraditi sa ovakvom strukturom:
(http://i.imgur.com/U9syalf.png)
Sledece sto radimo jeste na konekciji igraca proveravamo da li je igrac registrovan:
[pawn]public OnPlayerConnect(playerid)
{
new query[128], // ovde definisemo string za query koji cemo formatirati i poslati kao upit
name[MAX_PLAYER_NAME]; // uzimamo ime igraca u ovu varijablu
GetPlayerName(playerid, name, sizeof(name)); // uzimamo ime
mysql_format(handle, query, sizeof(query), "SELECT * FROM players WHERE name='%e' LIMIT 1", name); // ovaj upit uzima sve iz tablice sa vasim imenom, limit 1 znaci prekid pretrage na prvom nadjenom rezultatu
mysql_tquery(handle, query, "CheckPlayerData", "i", playerid); // ovo je "threaded query" uzima sve ovo i salje ga na sledecu funkciju na proveru
return true;
}[/pawn]
Sada treba da proverimo da li se u bazi nalazi neko sa ovim imenom, ako da saljemo ga na prijavu a u suprotnom na registraciju.
[pawn]forward CheckPlayerData(playerid);
public CheckPlayerData(playerid)
{
if(cache_num_rows() > 0)
{
// cache_num_rows > 0 znaci da u tablici postoji bar jedan rezultat sa tim imenom
// sada uzimamo password i salt iz tablice i saljemo igraca na prijavu
cache_get_value(0, "password", PlayerInfo[playerid][Password], 65);
cache_get_value(0, "salt", PlayerInfo[playerid][Salt], 11);
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Prijava", "Vas racun je pronadjen, molimo vas upisite lozinku da se prijavite:", "Dalje", "Izlaz");
}
else {
ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_INPUT, "Registracija", "Vas racun nije pronadjen, molimo vas upisite lozinku da se registrujete:", "Dalje", "Izlaz");
}
return true;
}
[/pawn]
Sada kad smo to sve zavrsili idemo na potvrdu dialoga, tj, verifikaciju lozinke i prijavljivanje igraca na njegov racun kao i registraciju:
[pawn]public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
switch(dialogid)
{
case DIALOG_LOGIN:
{
if(!response) return Kick(playerid);
new buf[65], name[MAX_PLAYER_NAME];
SHA256_PassHash(inputtext, PlayerInfo[playerid][Salt], buf, 65);
if(strcmp(buf, PlayerInfo[playerid][Password]) == 0)
{
// Password se podudara sa unesenim
// Sada treba da uzmemo ostale podatke
GetPlayerName(playerid, name, sizeof(name));
mysql_format(handle, query, sizeof(query), "SELECT * FROM players WHERE name = '%e' LIMIT 1", name);
mysql_tquery(handle, query, "OnAccountLoad", "i", playerid);
// sve smo zavrsili, u funkciji OnAccountLoad idete dalje
}
else
{
// ukoliko lozinka nije ispravna vracamo dialog
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Prijava", "Lozinka koju ste uneli nije ispravna, pokusajte ponovo:", "Dalje", "Izlaz");
}
}
case DIALOG_REGISTER:
{
if(!response) return Kick(playerid);
if(strlen(inputtext) < 6 || strlen(inputtext) > 20) // ako je lozinka kraca od 6 i duza od 20 karaktera vraca dialog
{
ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_INPUT, "Registracija", "Vas racun nije pronadjen, molimo vas upisite lozinku da se registrujete:", "Dalje", "Izlaz");
return true;
}
// kao sto sam rekao salt je random generisan key, sada cemo ga randomizirati:
for (new i = 0; i < 10; i++) {
PlayerInfo[playerid][Salt]
= random(100) + 50;
}
PlayerInfo[playerid][Salt][10] = 0;
SHA256_PassHash(inputtext, PlayerInfo[playerid][Salt], PlayerInfo[playerid][Password], 65);
// sada unosimo u bazu podatke igraca
new query[256], name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof(name));
mysql_format(handle, query, sizeof(query), "INSERT INTO players SET \
name='%e',\
password='%e',\
salt='%e',\
regdate=NOW()", name, PlayerInfo[playerid][Password], PlayerInfo[playerid][Salt]);
mysql_tquery(handle, query, "OnPlayerRegister", "d", playerid);
}
}
return true;
}[/pawn]
Ovde pravimo funkciju za login igraca
[pawn]forward OnAccountLoad(playerid);
public OnAccountLoad(playerid)
{
if(cache_num_rows() > 0) {
cache_get_value_int(0, "id", PlayerInfo[playerid][SQLID]);
cache_get_value(0, "regdate", PlayerInfo[playerid][RegDate], 65);
// ovde ispod dodajete spawn, novac i slicno
printf("SQLID: %d | Register date: %s", PlayerInfo[playerid][SQLID], PlayerInfo[playerid][RegDate]);
SendClientMessage(playerid, -1, "Dobrodosli nazad.");
}
return true;
}[/pawn]
Sada ovde uzimamo zadnji unesen id i dodelujemo ga igracu kako bi kasnije mogli updejtovati tablicu za njega i slicno
[pawn]forward OnPlayerRegister(playerid);
public OnPlayerRegister(playerid)
{
PlayerInfo[playerid][SQLID] = cache_insert_id(); // uzimamo id igraca koji je unesen u bazu
SendClientMessage(playerid, -1, "Registrovali ste se na server.");
return true;
}[/pawn]
Zadnje, na disconectu spremamo podatke, ovo je samo princip, ja nisam napravio novac, skin ili slicno da cuvamo, ali pokazacu vam opet
[pawn]public OnPlayerDisconnect(playerid)
{
new query[128];
mysql_format(handle, query, sizeof(query), "UPDATE players SET money='%d', level='%d', skin='%d' WHERE id='%d'",
PlayerInfo[playerid][Money], PlayerInfo[playerid][Level], PlayerInfo[playerid][Skin],
PlayerInfo[playerid][SQLID]);
mysql_tquery(handle, query);
return true;
}[/pawn]
Naravno, dok ova metoda nije nepravila preporucuje se da se podatci updejtuju nakon updejta varijable u komandi, funkciji i slicno, pa evo vam i primer jedan za to:
[pawn]command(setskin, playerid, params[]) {
new id,
skin,
query[128];
if(sscanf(params, "ui", id, skin)) return SendClientMessage(playerid, -1, "Koristi: /setskin [player id] [skin]");
if(skin < 0 || skin > 311) return SendClientMessage(playerid, -1, "Skin nije validan.");
PlayerInfo[id][Skin] = skin;
// sada updejtujemo igraca
mysql_format(handle, query, sizeof(query), "UPDATE players SET skin='%d' WHERE id='%d'", PlayerInfo[id][Skin], PlayerInfo[id][SQLID]);
mysql_tquery(handle, query);
return true;
}
[/pawn]
To bi bilo to, nadam se da sam vam dobro sve objasnio, ovo je prvi tutorial koji pisem i potrudio sam se da vam objasnim sve sto bolje i krace.
Za kreiranje ove skripte smo koristili:
- MySQL R41-2 (https://github.com/pBlueG/SA-MP-MySQL/releases)
Ukoliko budete imali nekih problema pisite slobodno, ovo nije filter skripta, ovo nije mod, ovo je tutorial, gledajte malo po netu kako mozda bolje mozete napraviti ovo,
ja ovo nisam testirao, nesto mi je doslo da napisem i evo napisao sam kroz pola sat
UPDATES:
Obrisana caching metoda pullanja podataka.
Threaded query kod logina.
Pohvale za trud, veoma koristan tutorijal ali tu ima 1 problem, malo ko na balkan sampu radi u MySql, ako ne i šire. Kod nas će MySql tek kasnije ući u modu, jer kasnimo u svemu hehe
Znam, bas sam zato i otvorio. Mozda se neko potrudi i procita.
Hmm slicno sam radio ja reg i log samo samo koristio mysql_pquery jest da i sam nzm koja je razlika
OdliÄno i korisno. :)
Jos jedna zamerka zasto si koristio non-threaded query gore kad si mogao sa threaded queryem odraditi kod ucitavanja podataka.
Proticaj razliku izmedju non-threaded i threaded query-a gde ih koristiti i kako http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_query (ZA SAMP).
Kod je ispravno napisan.
Svestan sam razlike
Nema potrebe, da je neka veca kolicina podataka pa i ajde, cela poenta threaded upita je da server nastavi sa syncovanjem ostalih stvari dok se query izvrsava, dok ovako treba da saceka koju milisekundu, sto ne predstavlja problem nikakav :P
Izvini molim te, a sta je ovde kopirano, osim verzije mysqla? Kako ne volim kad se neko pravi pametan, i slep covek vidi da sam se potrudio jos da objasnim sve do zadnje sitnice i jos da sve predloge sto su pisali ljudi realizujem i opet se nadje neki serator koji zna samo pametovati a realno dve ciste nema.