extDB3 Tutorial - Wie man aus ArmA3 auf mysql Daten zugreift

  • Hallo zusammen,


    gibt es schon irgendwo ein verständliches Tutorial zu extDB3? Ich hab keins gefunden. Daher starte ich mal hier eine entsprechende Diskussion die am Ende hoffentlich eine Art Tutorial wird, indem ich den ersten Beitrag wiederholt aktualisiere.


    Schritt 1: extDB3 Installation scheint trivial:

    • Neueste extDB3 Version runterladen
    • tbbmalloc_x64.dll und tbbmalloc.dll ins ArmA3 Hauptverzeichnis kopieren
    • @extDB3 Ordner ins ArmA3 Hauptverzeichnis kopieren (und ab sofort im serverseitigen Modstring mitanziehen)
    • extdb3-conf.ini im @extDB3 Ordner konfigurieren: Also IP, Port (meist 3306), Username, Passwort, Datenbankname eintragen

    Schritt 2: Interessant wirds bei der eigentlichen Verwendung in den sqf Skripten. Gehen wir mal von dem einfachsten Anwendungsfall aus: Zu Beginn einer Mission möchte ich Infos der vorherigen Mission laden. Am Ende der Mission möchte ich den Spielstand speichern. Ich brauche also serverseitig eine initDatenbank.sqf, ladeAusDatenbank.sqf und speicherInDatenbank.sqf.

    Code: initDatenbank.sqf
    1. "extDB3" callExtension "9:ADD_DATABASE:meinDatenbankKonfigName"
    2. "extDB3" callExtension "9:ADD_DATABASE_PROTOCOL:meinDatenbankKonfigName:SQL_CUSTOM:SQL"

    meinDatenbankKonfigName ist dabei der der Begriff in Klammern, der in der Datenbank Konfigurationsdatei oberhalb von IP, Passwort, DB-Name, Port steht. meinDatenbankKonfigName ist also NICHT unbedingt der mysql Datenbankname. Wenn man nur mit einer Datenbank arbeitet, kann man diese beiden Namen aber gleich setzen um keine Verwirrung aufkommen zu lassen.


    Das SQL_Custom Protokoll erzwingt/ermöglicht, dass man vordefinierte SQL Befehle in einer ausgelagerten Datei ordentlich zusammenfasst. Diese ausgelagerten Befehle werden dann über einen Variablennamen referenziert. Die ausgelagerte ini-Datei sieht dabei so aus.


    Code: speicherInDatenbank.sqf
    1. _ergebnisDesSpeicherversuchs = call compile ("extDB3" callExtension format ["0:meineProtokollID:variableDieAufSQLBefehlInSQLIniDateiVerweist:%1:%2", _parameter1, _parameter2]); // 0 steht für 'Sync'
    2. waitUntil {!(isNil "_ergebnisDesSpeicherversuchs ")};
    Code: ladeAusDatenbank.sqf
    1. _ergebnisAlsoDatensaetze = call compile ("extDB3" callExtension format ["0:meineProtokollID:getAllBoxes_based_on_map:%1", _parameter1]); // 0 steht für 'Sync'
    2. waitUntil {!(isNil "_ergebnisAlsoDatensaetze")};

    Was man dann anstellt mit dem Lade-Ergebnis ist jedem selbst überlassen. Das Ergebnis ist vermutlich ein Array nach diesem Muster [TYPE, DATA], wobei TYPE i.d.R. '1' sein sollte was für 'OK' steht. Ergebnis ist also meist das Array [1, datenAusDatenbank].

    Code: ladeAusDatenbank.sqf - Fortsetzung Datenaufbereitung
    1. tbd

    Gruß,
    Schmitt

  • Vielleicht will jemand ja einmal ein Beispiel sehen: Gist
    So nutze ich eine extDB3 Datenbank für eine Pilotenwhitelist.
    Es sind in dem gist noch weitere Funktionen, welche besonders dann interessant werden, wenn man größere Datenmengen verarbeiten will. Dann muss man den Asyncron Modus der extension nutzen.



    Off Topic: Falls jemand nen SQL Tipp hat, wie ich die bei [insertOrUpdatePlayerInfo] verhindere, dass mir durch das auto_increment die id auf dauer in das unendliche steigt, würde ich mich freuen ;-)

  • Super, verwende exDB selbst sehr intensiv.


    Zu Dorbedos Frage: beim Anlegen einer Tabelle kannst du autoincrement als Attribut mit angeben oder eben auch nicht. Dh konkret lass autoincrement weg. Dafür muss dann natürlich eine ID bei INSERT mit übergeben werden
    https://www.w3schools.com/sql/sql_autoincrement.asp


    Edit: Ich sehe aber gerade, dass du über auto_increment wohl schon Bescheid weißt. Dann hängt das Limit vom Datentyp ab, sprich richtest du den Primary Key als int ein, hat auto_increment eben 2^31-1 Möglichkeiten, ehe er von vorne beginnt:
    https://stackoverflow.com/ques…primary-key-in-sql-server


    Daher entweder einen sehr kleinen Datentyp wählen oder doch über obige Variante gehen und einfach die ID per SQF bestimmen lassen (so kann man ja die zuletzt vergebene abfragen oder andere Strategien relativ leicht implementieren)

  • Dieser Post teilt sich auf mehrere Unterposts auf, da die Zeichenbegrenzung von 10.000 nicht ausreicht


    Unser ZIel: Wir wollen Loadouts unserer Gemeinschaft in der DB ablegen. Dazu möchten wir die Loadouts einerseits speichern können, andererseits abrufen. Wir brauchen also sowohl INSERT-Befehle als auch SELECT-Befehle.
    Bei uns geschieht dies über einen Dialog, bei dem unter anderem zunächst der Zug, die GRuppe und schließlich das Uniformmuster (Fleck-, Tropentarn) gewählt werden kann. Diese Details werde ich hier auslassen.



    Datenbank


    Zunächst brauchen wir natürlich eine Datenbank. Gehen wir einmal davon aus, diese heißt gameserver und besitzt die Tabelle po_loadout mit folgendem Schema:





    Wie man sehen kann, ist es hier wichtig, sich eine saubere Struktur zu überlegen, mit der man das gewünschte Ziel erreichen kann. Mit dieser recht großen Anzahl an Spalten sind wir in der Lage, die gesamte Ausrüstung des Spielers sowie sämtliche Container und deren Inhalte zu erfassen sowie einige ACE spezifische Variablen. Bei der Spalte zug handelt es sich um einen Aufzählungstyp, um zu verhindern, dass hier neue Kategorien angelegt werden können oder beim Einfügen neue Kategorien durch Rechtschreibefehler fälschlicherweise hinzukommen.


    Der User für die Datenbank heißt ebenfalls gameserver und hat natürlich alle notwendigen Rechte für diese DB.

  • ExtDB3


    Beginnen wir mit der Configuration für extDB3.


    Die Datei extdb3-conf.ini muss um euren Datenbankzugang erweitert werden. Dies sähe z.B. so aus:


    Hier müsst ihr also lediglich eure Verbindungsdaten eintragen. Wichtig ist noch der Name [Loadouts], den wir später als Protokollname brauchen werden.


    Damit ist aber lediglich die Verbindung angelegt, was fehlt, sind die eigentlichen SQL-Befehle. Aus Sicherheitsgründen schreiben wir diese nicht in SQF bzw. erlauben keine custom SQL-Abfragen, sondern werden in einer eigenen .ini alle erlaubten Abfragen genau spezifizieren.


    Natürlich kann ich hier nicht alle notwendigen Abfragen zeigen, ich beschränke mich auf die für das Verständnis notwendigen INSERT und SELECT Abfragen.


    Dazu legen wir im Ordner sql_custom eine neue .ini-Datei an mit dem Namen loadout.ini und dem Inhalt




    Jede Abfrage besitzt einen eindeutigen Namen wie jgkp_loadout_insert, den wir ebenfalls später benötigen werden.


    Wie man sehen kann, ist der erste SELECT-Befehl recht umfangreich. Zwar gibt es nur einen Parameter, nämlich die ID des Datensatzes (der PK in der DB), allerdings müssen wir die Spalten angeben.Ich weiß aktuell nicht, warum dort nicht einfach SELECT * FROM `po_loadout` WHERE `id` = ?; steht, aber vielleicht hatte das einen Grund


    Übergeben wir also diesem Aufruf eine eindeutige ID, so bekommen wir alle aufgezählten Spalten als Antwort zurück.


    Ähnlich der INSERT-Befehl, hier ist es nur umgekehrt: Um ein neues Loadout in die DB zu speichern, lassen wir den ersten Parameter für die ID frei (leerer String), da die DB die ID automatisch inkrementiert, und übergeben den Inhalt für alle anderen Spalten.


    SQL1_INPUTS sagt, welcher übergebene Wert für welchen Platzhalter '?' eingesetzt wird. Übergebe ich drei Werte und schreibe SQL1_INPUTS = 1,2,3, so werden sie in der übergebenen Reihenfolge eingesetzt, SQL1_INPUTS = 3,2,1 würde die drei Werte in umgekehrter Reihenfolge einsetzen. Meistens sollte hier die aufsteigende Reihenfolge stehen.


    Gibt eine Anfrage etwas zurück, muss zusätzlich OUTPUT definiert werden. Hier gibt die Zahl die Nummer des zurückgegebenen Wertes an und danach kommt der Datentyp. Hier sind auch andere Dinge als STRING möglich, aber STRING ist am unkompliziertesten und man weiß immer, womit man arbeitet und spart sich umständliche Typabfragen später in SQF.

  • Programmierung im Spiel


    Damit sind die Vorbereitungen abgeschlossen. Wir können jetzt die Protokolle bzw. die Schnittstelle in ArmA nutzen. Dazu müssen wir zunächst sicherstellen, dass der Server oder Client eine Verbindung zur DB herstellen kann. Bei uns läuft die gesamte Datenbankanbindung über den Server und der Client schickt Anfragen per CBA EventHandler serverEvent.


    Die DB-Anbindung könnte dann wie folgt aussehen:



    Zunächst fügen wir die Datenbank hinzu:
    DB_connect_Loadouts = call compile ("extDB3" callExtension "9:ADD_DATABASE:Loadouts");


    Hier ist es wichtig, dass der Name Loadouts mit dem Namen des DB-Eintrages in der extdb3-conf.ini übereinstimmt (das war der Name in eckigen Klammern [...]).


    Anschließend müssen wir noch unsere custom .ini mit den SQL-Abfragen einfügen:


    PROTOCOL_connect_Loadout = call compile ("extDB3" callExtension "9:ADD_DATABASE_PROTOCOL:Loadouts:SQL_CUSTOM:loadoutquery:loadout.ini");


    Auch hier ist es wichtig, dass alle Namen übereinstimmen. Hinter dem ADD_DATABASE_PROTOCOL kommt der DB-Name, wie bereits bei ADD_DATABASE, hinter SQL_CUSTOM kommt hingegen ein selbstgewählter Name, mit dem wir später auf alle Abfragen der loadout.ini Zugriff haben. Der Name loadoutquery ist also selbst gewählt und Bezeichnet quasi das Protokoll. loadout.ini muss natürllich der Name eurer ini-Datei im Ordner sql_custom sein.


    Alle anderen Befehle stellen nur eine Abfrage dar, ob die Anbindung korrekt geklappt hat.


    Jetzt können wir die SQL-Abfragen verwenden:


    Zum Abfragen eines Loadouts brauchen wir lediglich eine ID in der einfachsten Variante, daher hier der entsprechende Server Eventhandler von CBA:



    Das Event bekommt als Übergabewert die ID des Loadouts in der DB. Zu Beginn wird aus Sicherheits- und Performancegründen gewartet, falls eine andere Datenbankabfrage noch im Gange ist. Die eigentliche Abfrage passiert in der Zeile 24:


    _query = call compile ("extDB3" callExtension format ["0:loadoutquery:jgkp_loadout_select_loadout:%1",_id]);



    Hier führen wir mittels callExtension eine Abfrage mit dem Protokoll loadoutquery aus, und hier spezifizieren wir jetzt, welche SQL-Abfrage gemein ist, nämlich jgkp_loadout_select_loadout. Diese muss also genau so in der loadout.ini hinterlegt sein. Insbesondere muss sie einen INPUT erwarten, da wir nach dem Namen der Abfrage mit :%1 abschließend einen Parameter an diese SQL-Abfrage übergeben. Der Rückgabewert wird mit einer Hilfsfunktion ausgewertet und in ResultLoadoutContent abgespeichert. Diese Variable wird per publicVariableClient an den aufrufenden Clienten zurückgesendet, der dann sein Loadout erhält. Der Vollständigkeit halber hier das Skript, was das Loadout dem Spieler zuweist.



    Kann man alles schöner machen, überarbeiten usw, das ist aber hier nicht das Thema Wie man sieht, geschieht am Anfang noch ein splitString. Das hängt damit zusammen, wie die Daten in der DB gespeichert werden. Wir speichern die Inhalte der Container z.B. folgendermaßen:





    Auf diese Weise sind Klassenname und benötigte Anzahl elegant und platzsparend notiert. Ein splitString am Zeichen ";" trennt einzelne Itemtypen, ein splitString am Zeichen ~ trennt Item-Klassenname und Anzahl. Hier muss jeder selbst schauen, welche Kombination konfliktfrei funktioniert. Das Zeichen : eignet sich beispielsweise nicht, da es mit extDB3 kollidiert, da extDB3 selbst ":" verwendet, wie oben gesehen, um Bestandteile zu trennen.

  • Umgekehrt sieht das Einfügen eines Loadouts ähnlich aus. Hier ist der Workflow genau umgekehrt. Wir müssen zuerst das Loadout eines SPielers abfragen, was das folgende Skript erledigt:



    Wie gesagt passiert das ganze via Dialog, weshalb einige Zeilen für die Abfrage der Dialog-Kontrollelemente notwendig sind. Der Kerninhalt passiert aber 52, wo Container für Container und Item für Item alles abgefragt wird, was der Spieler aktuell am Mann hat. Für uns interessant ist eigentlich nur die Übergabe an den Server:

    Code
    1. ["jgkp_save_loadout",[_unit,_zug,_gruppe,_typ,_name,_headgear,
    2. _goggles,_vest,_vestitems,_uniform,_uniformitems,_backpack,
    3. _backpackitems,_weapons,_weaponitems,_secweaponitems,
    4. _handgunitems,_items,_selectWeapon,_isMedic,_isEOD,_memberOnly,_isPublic]] call CBA_fnc_serverEvent;


    Wie wir sehen, werden eigentlich genau die Inhalte übergeben, die auch in der Datenbank als Spalten wiederzufinden sind. Alles was der Server noch machen muss, ist diesen Inhalt nehmen und an das entsprechende Protokoll übergeben:




    Wie bereits zuvor gesehen, passiert das Wesentliche in Zeile 25:
    _query = call compile ("extDB3" callExtension format ["0:loadoutquery:jgkp_loadout_insert:%1:%2:%3:%4:%5:%6:%7:%8:%9:%10:%11:%12:%13:%14:%15:%16:%17:%18:%19:%20:%21:%22", _zug,_gruppe,_typ,_name,_headgear,_goggles,_vest,_vestitems,_uniform,_uniformitems,_backpack,_backpackitems,_weapons, _weaponitems,_secweaponitems,_handgunitems,_items,_selectWeapon,_isMedic,_isEOD,_memberOnly,_isPublic]);


    Hier wird vom selbstgenannten Protokoll loadoutquery die SQL-Abfrage mit dem Namen jgkp_loadout_insert aufgerufen und ihr werden alle 22 notwendigen Parameter übergeben. Die ID ist nicht notwendig, da diese automatisch von der DB erhöht/vergeben wird, sofern gewünscht.



    Hoffe, dieses reale Anwendungsbeispiel hat die Zusammenhänge und Möglichkeiten etwas besser aufgezeigt. Alles Geschriebene kann frei verwendet werden.

  • Danke dir für dieses wirklich sehr ausführliche und sehr gut erklärte Beispiel. Ich werde mich morgen mal dran setzen und es schritt für schritt durcharbeiten.


    Eventuell hast du ja auch Lust dein SQF-Scriptguide um den Part Datenbanken und Arbeiten damit zu erweitern, dann hätte man immer alles auf einen Blick ins Handbuch. Fände ich eine echt coole Sache.

  • Guten Abend,

    ich suche für mich eine Möglichkeit Playerstatistiken zu speichern.

    Würde gerne Playername, Kills, Softkills, Armorkills, Airkills, Deaths und Totalscore für eine eigene Mission auf der DB speichern.

    (so wie in der Domination von Xeno)

    Also wenn die Mission neu gestartet wird sollen die Punkte der Player wieder erscheinen.

    extDB3 und Datenbank und Tabellen sind eingerichtet und läuft auch. Die custom.ini ist als txt im Anhang.

    Nur die Einbindung in die Mission verstehe ich nicht. (Wo die Abfragen laufen)

    Ich finde leider keine Anleitung.


    Für Tipps wäre ich dankbar.

    Gruß reamer

  • Hi reamer,


    du möchtest wahrscheinlich eine Reihe von Event Handlern (https://community.bistudio.com/wiki/Arma_3:_Event_Handlers) definieren, bspw. in einer der Inits (Initialization Order - Bohemia Interactive Community). Noch besser wäre du nutzt ein Framework wie CBA, dort kannst du leichter Events hinzufügen. Ich habe hier ein einfaches Beispiel mit extDB3 Anbindung: kellerkompanie-mods/XEH_preInit.sqf at master · kellerkompanie/kellerkompanie-mods · GitHub


    Gruß,

    Schwaggot