MySQL R38 - Simples Anmeldung´s System

Wichtiger Hinweis: Bitte ändert nicht manuell die Schriftfarbe auf schwarz sondern belasst es bei der Standardeinstellung. Somit tragt ihr dazu bei dass euer Text auch bei Verwendung unseren dunklen Forenstils noch lesbar ist!

Tipp: Ihr wollt längere Codeausschnitte oder Logfiles bereitstellen? Benutzt unseren eigenen PasteBin-Dienst Link
  • Guten Abend,


    Einleitung:


    Mein Name lautet MrPawn ( Michael ).
    Heute biete ich euch ein Simples Tutorial über ein Anmeldung´s System mit der Version R38.
    Aktuell gibt es auch die Version R39, aber das Prinzip wird sich nicht viel verändert haben.
    Bei dem Update von R38 - 39 wurden höchstens ein paar Korigierungen vorgenommen.
    So nun wollen wir aber mal anfangen mit der Anleitung für ein Simples Anmeldungs System.
    Ob es 100% Anfängertäuglich ist stellt sich zu einem späteren Zeitpunkt heraus.


    Voraussetzungen:


    Bevor wir mit diesem System anfangen wollen, müssen wir zuerst einmal wissen was ich alles dafür brauchen.
    Die Voraussetzungen werde ich nun auflisten.


    • Das MySQL Include in der Version r38/39.
    • Das jeweilige bereitgestellte MySQL Plugin in der Version R38.
    • Einen beliebigen Windows/Linux SA-MP Server.
    • Eine MySQL Datenbank, zur Testzwecken reicht eine ganz normale Lokale XAMPP Datenbank.
    • Leichte Grundkenntnisse.


    Installation:


    Was wir zuerst machen werden, ist folgendes: Da ich ja das ganze auf einen Windows Server laufen lasse, werde ich nun die libmysql.dll in die Hauptverzeichnis vom Server ziehen.

    Das gleiche machen wir mit der mysql.dll:
    Nur bei ihr sieht das ganze anders aus:
    Wir dürfen nähmlich das Plugin nicht in das Hauptverzeichnis klatschen.
    Sondern müssen die mysql.dll in den plugins Ordner verschieben.

    Dann muss die .inc Datei in /pawno/include/ verschoben werden.

    Zuletzt muss das ganze einfach nur bei der server.cfg unter "plugins" enzutragen.

    Wie man die XAMPP Datenbank Lokal hosten kann , werd ich allerdings nicht zeigen.


    Verbindungsaufbau zu der MySQL Datenbank:


    Da R38 immer nach einer ConnectingHandle fragen wird, werden wir uns eine Variable erstellen.
    Das sollte weiter oben im Script erfolgen.
    new Handle;
    Dann müssen wir überlegen , aber welchem Zeitpunkt soll eine Verbindung hergestellt werden.
    Das ist einfach zu beantworten.
    Wenn der Server startet, soll eine Verbindung aufgebaut werden.
    Wo kann man den Start sozusagen festlegen, das jeweilige Callback lautet: OnGameModeInit.
    Aber voher um es leichter zu machen, erstellen wir uns ganz normal definierungen für die Datenbank.
    Die können wir beliebig nennen, solange sie auf einen String zuweisen, sprich: ""


    #define MYSQL_HOST "127.0.0.1"
    #define MYSQL_USER "root"
    #define MYSQL_DATA "tutorialdb"
    #define MYSQL_PASS ""


    So nun können wir beim "Serverstart Callback" die Verbindung versuchen herzustellen.
    Dazu müssen wir die Funktion mysql_connect anwenden, auf diesen Connect müssen wir gleichzeitig unsere erstellte Variable "Handle" zuweisen.
    Wie das gehen würde sehr ihr hier:
    http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_connect


    public OnGameModeInit()
    {
    Handle = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DATA, MYSQL_PASS);
    return 1;
    }


    So aber am besten sollte man sich 2 Sachen vergewissern, 1. Das die Verbindung steht, 2. Das der Verlauf geloggt wird.
    Um zu wissen, das die Verbindung steht müssen wir mit der Funktion mysql_errno Arbeiten.
    http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_errno


    Verbindungstest:
    public OnGameModeInit()
    {
    Handle = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DATA, MYSQL_PASS);
    if(mysql_errno() < 1)print("MySQL: Die Verbindung wurde erfolgreich hergestellt."); else print("MySQL: Die Verbindung zur MySQL Datenbank konnte nicht hergestellt werden | Der Server wird nun heruntergefahren."), SendRconCommand("exit");
    return 1;
    }


    So sind wie auf der sicheren Seite.


    Jetzt müssen wir dafür sorgen das ganze auch geloggt wird, deswegen Arbeiten wir mit der Funktion mysql_log.
    http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_log
    Es gibt 5 Verschiedene Arten von Logs.


    Typen:

    Code
    LOG_NONE	 Logs absolutely nothing.
    LOG_ERROR	 Logs errors.
    LOG_WARNING	 Logs warnings.
    LOG_DEBUG	 Logs debug messages.
    LOG_ALL	         Logs everything.


    Da wir nur die mal die ganze LOG im Blick haben, nehmen wir Type 5.
    Das ganze wenden wir nun so an:
    public OnGameModeInit()
    {
    Handle = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DATA, MYSQL_PASS);
    if(mysql_errno() < 1)print("MySQL: Die Verbindung wurde erfolgreich hergestellt."),mysql_log(LOG_ALL); else print("MySQL: Die Verbindung zur MySQL Datenbank konnte nicht hergestellt werden | Der Server wird nun heruntergefahren."), SendRconCommand("exit");
    return 1;
    }


    So, Leute somit währe der Verbindungsaufbau vollbracht.


    Query:


    Jetzt werden wir mal mit Querys Arbeiten.
    Ein Query ist sozusagen eine Abfrage, die aus der Datenbank erfolgt.
    Wir werden jetzt eine Abfrage starten, ob ein Eintrag mit unserem namen in der Datenbank existiert.
    Wir gehen also zum Callback was aufgerufen sobald ein Spieler auf den Server connected.
    Dieses Callback nennt sich: OnPlayerConnect


    public OnPlayerConnect(playerid)
    {
    return 1;
    }


    So nun erstellen wir uns einen neuen String, namens "query" ( muss nicht umbedingt sein, so mache ich das halt. )


    public OnPlayerConnect(playerid)
    {
    new query[256];
    return 1;
    }


    So nun müssen wir den Query/String befüllen lassen, sogesehen auch formatieren.
    Das machen wir mit der Funktion format


    format(query, sizeof(query), "..", ..);


    So nun müssen wir auch wissen welchen befehl wie der abfrage für die Datenbank zuweisen sollen.
    Aber was auch ganz wichtig ist, MySQL Befehle werden "IMMER!" Groß geschrieben.
    Mit dem Command SELECT können wir von einer Tabele eine jeweilge Spalte durchsuchen nach werten,strings.
    Wir müssen jeweils auch mit FROM & WHERE arbeiten , damit die DB auch weiß , was & wo sie suchen soll.


    Ich mache hiermal einen beispiel code:


    QUERY:


    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", ..);


    So zuerst einmal muss das nicht user heißen sondern kann beliebig heißen, es muss halt nur in der Datenbank erstellen werden.
    Dann müssen wir eine Variable mit dem jeweiligen Spielernamen füllen lassen. Was auch gehen würde wäre man sich gleich dafür eine Funktion erstellen würde.
    Ich zeige mal beide beispiele:


    1. Variante:
    public OnPlayerConnect(playerid)
    {
    new query[256], name[24];
    GetPlayerName(playerid, name, 24);
    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", name);
    return 1;
    }


    2. Variante:


    public OnPlayerConnect(playerid)
    {
    new query[256];
    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", Spielername(playerid));
    return 1;
    }


    stock Spielername(playerid)
    {
    new name[24];
    GetPlayerName(playerid, name, 24);
    return name;
    }


    Aber wenn wir das so lassen würden, dann würde es nicht ganz gut Klappen.
    Aus dem einfachen Grund, das MySQL wenn es schon ein String ist, einen escaped.
    Deswegen arbeiten wir mit der Funktion mysql_escape_string.
    http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_escape_string
    Dies ist nicht wirklich schwer.
    Ich zeigs wieder mit 2 Varianten:


    1. Variante:
    mysql_escape_string(name, name);


    2. Variante:
    mysql_escape_string(Spielername(playerid), Spielername(playerid));


    public OnPlayerConnect(playerid)
    {
    new query[256];
    mysql_escape_string(Spielername(playerid), Spielername(playerid));
    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", Spielername(playerid));
    return 1;
    }


    So nun müssen wir den Query auch absenden, dass machen wir mit der Funktion mysql_tquery
    http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_tquery


    public OnPlayerConnect(playerid)
    {
    new query[256];
    mysql_escape_string(Spielername(playerid), Spielername(playerid));
    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", Spielername(playerid));
    mysql_tquery(Handle, query, "UserCheck", "i", playerid);
    return 1;
    }


    So ich denke, Handle & Query lassen sich von selbst erklären aber nicht "UserCheck, i & playerid".
    UserCheck wird als Callback verwendet (forward & public).
    Das ist i steht für Integer.
    Auf deutsch übersetzt ganzzahlige werte.
    Und i wird dann halt mit playerid gefüllt.


    So nun erstellen wir uns das Callback UserCheck, INFO: Kann wieder beliebig genannt werden.


    forward UserCheck(playerid);
    public UserCheck(playerid)
    {
    }


    So nun müssen wir wissen, wie wir die Ergebnisse fragen, das ist Einfach. Wir müssen mit der Funktion cache_get_data 2 Variablen füllen lassen, und die einfach er 'if' frage prüfen.
    Dazu erstellen wir auch noch weit oben im Script 2 Dialoge


    #define DIALOG_REGISTER 1
    #define DIALOG_LOGIN 2


    http://wiki.sa-mp.com/wiki/MySQL/R33#cache_get_data


    forward UserCheck(playerid);
    public UserCheck(playerid)
    {
    new num_rows, num_fields;
    cache_get_data(num_rows, num_fields, Handle);
    if(num_rows == 0)
    {
    //Register..
    ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Anmeldung", "Es wurde kein Account unter diesem namen gefunden!", "Anmelden", "Abbrechen");
    }
    else
    {
    //Login..
    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Anmeldung", "Es wurde ein Account unter diesem namen gefunden!", "Anmelden", "Abbrechen");
    }
    }


    So nun müssen wir zum dem Callback was für die Antwort, es eines Buttons von einem Dialog ist. Eine sogenannte Response.
    Das Callback nennt sich OnDialogResponse


    public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
    {
    return 1;
    }


    Da fragen wir erstmal die jeweilige DialogID ab.


    public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
    {
    new query[256], key[50];
    if(dialogid == DIALOG_REGISTER)
    {
    if(!response)
    {
    return Kick(playerid);
    }
    if(!strlen(inputtext) < 4)return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Anmeldung", "Es wurde kein Account unter diesem namen gefunden!", "Anmelden", "Abbrechen");
    format(key, 50, "%s", inputtext);
    mysql_escape_string(Spielername(playerid), Spielername(playerid)), mysql_escape_string(key, key);
    format(query, sizeof(query), "INSERT INTO (username, passwort) VALUES ('%s',MD5('%s')", Spielername(playerid), key);
    mysql_tquery(Handle, query);
    SendClientMessage(playerid, -1, "Dein Account wurde erstellt.");
    GivePlayerMoney(playerid, 50000), SetPlayerScore(playerid, 10);
    }
    return 1;
    }


    Jetzt fragen sich bestimmt die neulinge, wieso ich MD5('..') gemacht habe.
    MD5 ist eine Verschlüsseung für string. MySQL liefert die Funktion schon mit sich.


    So jetzt kümmern wir uns die Login Funktion:


    public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
    {
    if(dialogid == DIALOG_LOGIN)
    {
    if(!response)
    {
    return Kick(playerid);
    }
    format(key, 50, "%s", inputtext);
    mysql_escape_string(Spielername(playerid), Spielername(playerid)), mysql_escape_string(key, key);
    format(query, sizeof(query), "SELECT * FROM user WHERE username='%s' AND password='%s'", Spielername(playerid), key);
    mysql_tquery(Handle, query, "OnPasswordResponse", "i", playerid);
    }
    return 1;
    }


    forward OnPasswordResponse(playerid);
    public OnPasswordResponse(playerid)
    {
    new num_fields, num_rows;
    cache_get_data(num_rows, num_fields);
    if(num_rows == 0)
    {
    //Passwort falsch..
    SendClientMessage(playerid, -1, "Das Passwort ist inkorrekt.");
    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Anmeldung", "Es wurde ein Account unter diesem namen gefunden!", "Anmelden", "Abbrechen");
    }
    else
    {
    //Passwort richtig..
    SendClientMessage(playerid, -1, "Erfolgreich eingeloggt.");
    SpielerInfo[playerid][pEingeloggt] = true;
    LoadAccount(playerid);
    SpawnPlayer(playerid);
    }
    return 1;
    }


    Wir erstellen nun wieder weiter oben im Script 1 Enum + 1 Variable


    enum PD {
    pEingeloggt,
    pGeld,
    pLevel,
    };


    new SpielerInfo[MAX_PLAYERS][PD];


    stock LoadAccount(playerid)
    {
    SpielerInfo[playerid][pEingeloggt] = true;
    SpielerInfo[playerid][pGeld] = cache_get_field_content_int(0, "Geld", Handle), GivePlayerMoney(playerid, SpielerInfo[playerid][pGeld]);
    SpielerInfo[playerid][pLevel] = cache_get_field_content_int(0, "Level", Handle), SetPlayerScore(playerid, SpielerInfo[playerid][pLevel]);
    return 1;
    }


    So, nun kommen wir fast zum Ende. Am Ende wirkt es vielleicht ein bisschen wie c & p aber wenn die Leute dieses Tutorial hier sehen und das wirklich lernen wollen, dann werden sie schon nicht nur c&p machen.


    Ab zur SpielerSpeichern Funktion.


    Nun müssen wir überlegen, wo soll die hin? Das ist Simpel, wie alles andere hier auch:


    public OnPlayerDisconnect(playerid)
    {
    SpielerSpeichern(playerid);
    return 1;
    }


    stock SpielerSpeichern(playerid)
    {
    if(SpielerInfo[playerid][pEingeloggt] == false)return 1;
    mysql_escape_string(Spielername(playerid), Spielername(playerid));
    format(query, sizeof(query), "UPDATE user SET Geld='%i',Level='%i' WHERE username='%s'", GetPlayerMoney(playerid), GetPlayerScore(playerid), Spielername(playerid));
    mysql_tquery(Handle, query);
    return 1;
    }


    So Leute das war es eigentlich im großen & ganzen.



    DOWNLOADS
    Komplettes MySQL Pack ( R38 ) Windows: ► Direkt Download
    Komplettes MySQL Pack ( R38 ) Windows: ► File Upload (Link Down, wird erneuert)
    Komplettes MySQL Pack ( R38 ) Windows: ► Dropbox


    Mit Freundlichen Grüßen,


    MrPawn

    6 Mal editiert, zuletzt von Kasakow ()

  • public OnPlayerConnect(playerid) { new query[256]; format(query, sizeof(query), "SELECT * FROM user WHERE username='%s'", Spielername(playerid)); return 1; } stock Spielername(playerid) { new name[24]; GetPlayerName(playerid, name, 24); return name; }

    Die Größe der Variable query ist unnötig groß.
    SELECT * FROM user WHERE username='%s' sind 38 Zeichen + MAX_PLAYER_NAME (new query[38+MAX_PLAYER_NAME];)


    Gut, zu dem Rest sage ich nichts da es ja für Anfänger ist, aber bspw. solche Sachen

    if(!response) { return Kick(playerid); }

    Hättest du auch so machen können ^^
    if(!respone)return Kick(playerid);


    Trotzdem ganz nett erklärt.

  • Warum erstellst du dir eine Funktion um den Namen aufzurufen ?
    Stell dir vor du hast 50 Spieler, pro Spieler alleine beim LogIn und LogOut schon 3 Aufrufe (u.a. beim fehlerhaften Login +2).
    5 * 50 = 250 | Im Laufe des Gamemodes wirst du die Funktion dann wohl öfter's anwenden, sagen wir mal pro Spieler 200 á 10 Aufrufe (BEISPIEL!), sind schon 2000 + die 250, gesamt 20.000 Aufrufe ^^, das nur bei 50 Spielern. Klar es sind frei erfundene Zahlen, aber naja trotzdem nicht ganz so gut :p


    Da reicht's einfach ein p_Name[MAX_PLAYER_NAME] dem Enum (für die Spielerdaten) hinzuzufügen und einfach bei OnPlayerConnect, GetPlayerName(playerid, SpielerInfo[playerid][p_Name],MAX_PLAYER_NAME);

  • do.de - Domain-Offensive - Domains für alle und zu super Preisen
  • Ich persönlich bin für eine Anleitung, denn bei einer Anleitung ist alles viel verständlicher und nachvollziehbar.


    Meinst du nun wie es aktuell ist oder +Video anleitung ?


    Ich muss das Zeug halt auch Produzieren. Dadurch geht Zeit verloren.
    Aber wenn die Leute dadurch besser lernen, nehme ich diese Zeit gerne in Anspruch.


  • Meinst du nun wie es aktuell ist oder +Video anleitung ?


    Ich muss das Zeug halt auch Produzieren. Dadurch geht Zeit verloren.
    Aber wenn die Leute dadurch besser lernen, nehme ich diese Zeit gerne in Anspruch.

    Wäre wirklich Super und wäre für mich selbst etwas besser und ich wäre dir auch dankbar :)
    Zum aktuellen ein Video

  • Ich finde es gut das du solch ein Tutorial machst zwar ist es noch ausbaufähig.


    Jedoch mach ich noch paar Anmerkungen/Informationen:


    • Manche haben das Problem mit gewissen .dll`s (msvcp110.dll, msvcr110.dll etc...), diese muss kann man downloaden und in den Samp-Server-Ordner verschieben oder ganz einfach microsoft visual c++ redistributable updaten.
    • Ebenso kannst du noch ein Beispiel hinzunehmen wie man einen String ausließt das wissen auch nicht die meisten.
    • cache_get_data(num_rows, num_fields); kann man nutzen jedoch gibt es schon lange cache_get_field_count(); was um einiges effizenter ist.#
    • Nutze mysql_pquery anstelle von mysql_tquery da dieses um einiges schneller ist.
    • Zusätzlich kannst du bei mysql_connect die Anzahl der maximalen Verbindungen als Threads einstellen.
  • Hm, bei mir will irgentetwas mit dem Enum nicht stimmen kann mir hier kurz einer helfen?



    new SpielerInfo[MAX_PLAYERS][PD];


    enum PD
    {
    pEingeloggt,
    pGeld,
    pLevel,
    pAdminlevel
    };


    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(48) : error 017: undefined symbol "PD"
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(48) : error 009: invalid array size (negative, zero or out of bounds)
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(782) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1002) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1139) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1140) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1140) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1141) : warning 213: tag mismatch
    C:\Users\Patrice\Desktop\Scripting, Coding usw\Server\Test Befehle\gamemodes\Befehle.pwn(1141) : warning 213: tag mismatch


    //EDIT Anmerkung:
    Das Tag MisMatch kommt durch das enum


    //EDIT2:
    Enum hinzugefügt

  • Danke dafür,


    Aber ich bekomme immer wieder diesen Error in der Mysql_log.txt

    SQL
    [11:25:00] [ERROR] CMySQLConnection::Connect - (error #1044) Access denied for user ''@'localhost' to database 'befehledb'