extDB3 Tutorial - Wie man aus ArmA3 auf mysql Daten zugreift

  • Multiplayer
  • 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.

    Quellcode: 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.

    Quellcode: 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 ")};

    Quellcode: 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].

    Quellcode: ladeAusDatenbank.sqf - Fortsetzung Datenaufbereitung

    1. tbd
    Gruß,
    Schmitt


    Bei Interesse am BW Mod/ArmA3/ACE3 empfehle ich die Events des Jägerzug Achilles I
  • 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 ;)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Dorbedo ()

  • 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
    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:
    stackoverflow.com/questions/26…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)
    Kompaniechef der 3.Jägerkompanie
    Autor des SQF-Scriptquide

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von [3.JgKp]James ()

  • 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.
    Kompaniechef der 3.Jägerkompanie
    Autor des SQF-Scriptquide

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von [3.JgKp]James ()

  • 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:
    Spoiler anzeigen


    SQL-Abfrage: extdb3-conf.ini

    1. [Loadouts]
    2. IP = <IP des mySQL-Datenbankservers>
    3. Port = 3306
    4. Username = gameserver
    5. Password = <pw des Benutzers>
    6. Database = gameserver



    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
    Spoiler anzeigen

    SQL-Abfrage: loadout.ini

    1. [Default]
    2. Version = 12
    3. Strip Chars = ""
    4. ;; List of characters to strip out
    5. Strip Chars Mode = 1
    6. ;; 0 = Strip Bad Chars, 1 = Strip + Log Bad Chars, 2 = Return Error & Log Bad Chars
    7. ;; Note: Logging on works when sending data to database.
    8. Input SQF Parser = false
    9. ;; Expermential
    10. ;; If enabled will use SQF Array Parser instead of : seperator for values
    11. ;; i.e 0:SQL:UpdatePlayer:["Joe",[1,2,0],0.22333,"PlayerBackpack",-3]
    12. ;; Advantage is that you dont need to strip : seperator from user inputted values
    13. ; --------------------------------------------------------------------------------
    14. ; SQL Statements
    15. ; get = all data
    16. ; select = specific data depending on passed arguments
    17. ; update = update row
    18. ; delete = delete row
    19. ; insert = insert row
    20. ; check = boolean query
    21. ; --------------------------------------------------------------------------------
    22. ;; ---------- PO_LOADOUT Queries --------
    23. ;; start with jgkp_loadout
    24. [jgkp_loadout_select_loadout]
    25. SQL1_1 = SELECT
    26. SQL1_2 = `zug`,`gruppe`,`typ`,`name`,`headgear`,`goggles`,`vest`,`vestitems`,`uniform`,`uniformitems`,`backpack`,`backpackitems`,`weapons`,`primaryweaponitems`,`secondaryweaponitems`,`handgunitems`,`assignitems`,`selectWeapon`,`isMedic`,`isEOD`,`memberOnly`,`isPublic`
    27. SQL1_3 = FROM `po_loadout` WHERE id = ?;
    28. SQL1_INPUTS = 1
    29. OUTPUT = 1-String,2-String,3-String,4-String,5-String,6-String,7-String,8-String,9-String,10-String,11-String,12-String,13-String,14-String,15-String,16-String,17-String,18-String,19-String,20-String,21-String,22-String
    30. [jgkp_loadout_update]
    31. SQL1_1 = UPDATE `po_loadout` SET
    32. SQL1_2 = `zug` = ?, `gruppe` = ?, `typ` = ?, `name` = ?, `headgear` = ?, `goggles` = ?, `vest` = ?, `vestitems` = ?, `uniform` = ?, `uniformitems` = ?, `backpack` = ?, `backpackitems` = ?, `weapons` = ?, `primaryweaponitems` = ?, `secondaryweaponitems` = ?, `handgunitems` = ?, `assignitems` = ?, `selectWeapon` = ?, `isMedic` = ?, `isEOD` = ?, `memberOnly` = ?, `isPublic` = ?
    33. SQL1_3 = WHERE `id` = ?;
    34. SQL1_INPUTS = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
    35. [jgkp_loadout_delete]
    36. SQL1_1 = DELETE FROM `po_loadout` WHERE
    37. SQL1_2 = `id` = ?;
    38. SQL1_INPUTS = 1
    39. [jgkp_loadout_insert]
    40. SQL1_1 = INSERT INTO `po_loadout`
    41. SQL1_2 = VALUES (
    42. SQL1_3 = '',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
    43. SQL1_INPUTS = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22
    44. Return InsertID = true
    Alles anzeigen





    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.
    Kompaniechef der 3.Jägerkompanie
    Autor des SQF-Scriptquide

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von [3.JgKp]James ()

  • 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:

    Spoiler anzeigen


    C-Quellcode: db_conncect.sqf

    1. /** CALL TYPE
    2. 0 = Sync
    3. 1 = ASync (Doesnt save/return results, use for updating DB Values)
    4. 2 = ASync + Save (Returns ID, for use with 5)
    5. 4 = Get (Retrieve Single Part Message)
    6. 5 = Get (Retrieves Multi-Msg Message)
    7. 9 = System Commands
    8. */
    9. /** TYPE
    10. 0=ERROR (When Error encounter, data = basic info)
    11. 1=OK
    12. 2=ID (ID = ID for ASYNC / Multi-part Message)
    13. 3=WAIT (WAIT, means extDB hasn't got result yet)
    14. 5=MULTIMSG (When you call 4:, it will return [5] if message is Multi-part)
    15. */
    16. diag_log "[Sk3yN3t]: Begin establishing connection with DB";
    17. // RESET DB
    18. "extDB3" callExtension "9:RESET";
    19. // DB-Anbindung herstellen und Protokoll für eigene SQL-Statements anlegen
    20. DB_connect_Loadouts = call compile ("extDB3" callExtension "9:ADD_DATABASE:Loadouts");
    21. // Protokolle
    22. PROTOCOL_connect_Loadout = call compile ("extDB3" callExtension "9:ADD_DATABASE_PROTOCOL:Loadouts:SQL_CUSTOM:loadoutquery:loadout.ini");
    23. // lock DB for system commands beginning with 9:
    24. "extDB3" callExtension "9:LOCK:unlock_me";
    25. // Fehlerbehandlung bei gescheiterter Verbindung
    26. if ((DB_connect_Loadouts select 0 == 0) and !(DB_connect_Loadouts select 1 isEqualTo "Already Connected to Database")) exitWith {diag_log format["[Sk3yN3t]: error with ADD_DATABASE: %1", DB_connect_Loadouts select 1]};
    27. diag_log "[Sk3yN3t]: DB-Anbindung erfolgreich!";
    28. if ((PROTOCOL_connect_Loadout select 0 == 0) and !(PROTOCOL_connect_Loadout select 1 isEqualTo "Error Protocol Name Already Taken")) exitWith {diag_log format["[Sk3yN3t]: error with ADD_DATABASE_PROTOCOL: %1", PROTOCOL_connect_Loadout select 1]};
    Alles anzeigen






    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:

    Spoiler anzeigen

    C-Quellcode: jgkp_get_loadout

    1. ["jgkp_get_loadout", {
    2. /*
    3. ["jgkp_get_loadout",[id]] call CBA_fnc_serverEvent;
    4. Liefert den Inhalt des Loadouts für die gegebene ID (PK in DB).
    5. Übergabewert: id (int): PK in der DB
    6. Rückgabewert: Gibt den gesamten DB-Inhalt für die gewählte Zeile aus
    7. */
    8. JGKP_var_startParams = _this;
    9. ResultLoadoutContent = nil;
    10. (_this) spawn {
    11. params ["_id"];
    12. //busy wait
    13. if (not isNil "serverRunningQuery") then {
    14. waitUntil{ not serverRunningQuery}
    15. };
    16. serverRunningQuery = true;
    17. _query = call compile ("extDB3" callExtension format ["0:loadoutquery:jgkp_loadout_select_loadout:%1",_id]);
    18. _result = [_query, 1] call fnc_check_result; // Ergebnis der Tiefe 1
    19. serverRunningQuery = false;
    20. ResultLoadoutContent = _result;
    21. };
    22. }] call CBA_fnc_addEventHandler;
    Alles anzeigen



    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.

    Spoiler anzeigen

    C-Quellcode: fn_equipSoldier

    1. /*
    2. Author: James
    3. Version: 1.10
    4. Date: 2015-12-15
    5. Purpose: Rüstet einen Spieler mit dem Loadout aus der DB aus
    6. Übergabeparameter: loadout (list): Array aus Strings, das die Ausrüstung enthält
    7. Rückgabewert: None
    8. Verwendung: [loadout] execVM "equipSoldier.sqf"
    9. */
    10. // private nicht erforderlich: spawn erzeugt immer neuen stack
    11. // 0 bis 3 stehen für id, zug, gruppe und typ
    12. _headgear = (_this select 4);
    13. _goggles = (_this select 5);
    14. _vest = (_this select 6);
    15. _vestitems = (_this select 7) splitString ';';
    16. _uniform = (_this select 8);
    17. _uniformitems = (_this select 9) splitString ';';
    18. _backpack = (_this select 10);
    19. _backpackitems = (_this select 11) splitString ';';
    20. _weapons = (_this select 12) splitString ';';
    21. _weaponitems = (_this select 13) splitString ';';
    22. _secweaponitems = (_this select 14) splitString ';';
    23. _handgunitems = (_this select 15) splitString ';';
    24. _items = (_this select 16) splitString ';';
    25. _selectWeapon = (_this select 17);
    26. _isMedic = parseNumber (_this select 18);
    27. _isEOD = parseNumber (_this select 19);
    28. // full reset
    29. removeAllWeapons player;
    30. removeAllItems player;
    31. removeAllAssignedItems player;
    32. removeBackpack player;
    33. removeHeadgear player;
    34. removeGoggles player;
    35. removeUniform player;
    36. removeVest player;
    37. // items
    38. {
    39. player linkItem _x;
    40. } foreach _items;
    41. // weapons
    42. {
    43. player addWeapon _x;
    44. } forEach _weapons;
    45. {
    46. player addPrimaryWeaponItem _x;
    47. } forEach _weaponitems;
    48. {
    49. player addSecondaryWeaponItem _x;
    50. } forEach _secweaponitems;
    51. {
    52. player addHandgunItem _x;
    53. } forEach _handgunitems;
    54. // headgear
    55. if (_headgear != '') then {
    56. player addHeadgear _headgear;
    57. };
    58. // goggles
    59. if (_goggles != '') then {
    60. player addGoggles _goggles;
    61. };
    62. // uniform
    63. if (_uniform != '') then {
    64. player forceAddUniform _uniform;
    65. clearItemCargoGlobal (uniformContainer player);
    66. clearMagazineCargoGlobal (uniformContainer player);
    67. clearWeaponCargoGlobal (uniformContainer player);
    68. {
    69. _temp = _x splitString '~';
    70. _item = _temp select 0;
    71. _count = parseNumber (_temp select 1);
    72. (uniformContainer player) addItemCargoGlobal [_item, _count];
    73. } forEach _uniformitems;
    74. };
    75. // vest
    76. if (_vest != '') then {
    77. player addVest _vest;
    78. clearItemCargoGlobal (vestContainer player);
    79. clearMagazineCargoGlobal (vestContainer player);
    80. clearWeaponCargoGlobal (vestContainer player);
    81. {
    82. _temp = _x splitString '~';
    83. _item = _temp select 0;
    84. _count = parseNumber (_temp select 1);
    85. (vestContainer player) addItemCargoGlobal [_item, _count];
    86. } forEach _vestitems;
    87. };
    88. // backpack
    89. // first remove pseudo backpack
    90. removeBackpack player;
    91. if (_backpack != '') then {
    92. player addBackpack _backpack;
    93. clearItemCargoGlobal (backpackContainer player);
    94. clearMagazineCargoGlobal (backpackContainer player);
    95. clearWeaponCargoGlobal (backpackContainer player);
    96. {
    97. _temp = _x splitString '~';
    98. _item = _temp select 0;
    99. _count = parseNumber (_temp select 1);
    100. (backpackContainer player) addItemCargoGlobal [_item, _count];
    101. } forEach _backpackitems;
    102. };
    103. // ACE EOD settings
    104. if (_isEOD == 1) then {
    105. player setVariable ['ACE_IsEOD',true,true];
    106. };
    107. // ACE Medic settings
    108. player setVariable ['ace_medical_medicClass', _isMedic, true];
    109. player selectWeapon _selectWeapon;
    Alles anzeigen



    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:

    Spoiler anzeigen

    Quellcode

    1. PBW_RevisionF_Dunkel~1;BWA3_G_Combat_Clear~1;rhs_mag_30Rnd_556x45_Mk318_Stanag~4;rhs_mag_30Rnd_556x45_M855A1_Stanag_Tracer_Green~4;BWA3_optic_NSV600~1;BWA3_15Rnd_9x19_P8~1;ACE_HuntIR_monitor~1;ACE_DAGR~1;




    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.
    Kompaniechef der 3.Jägerkompanie
    Autor des SQF-Scriptquide
  • 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:

    Spoiler anzeigen

    C-Quellcode: fn_saveLoadout.sqf

    1. /*
    2. Author: James
    3. Version: 1.00
    4. Date: 2015-09-22
    5. Purpose: Speichert das aktuell ausgerüstete Loadout in der DB
    6. Übergabeparameter: None
    7. Rückgabewert: None
    8. Verwendung: wird NUR vom Dialog aufgerufen!
    9. */
    10. disableSerialization;
    11. private ["_zug","_gruppe","_typ","_name","_headgear",
    12. "_goggles","_vest","_vestitems","_uniform","_count","_backpack",
    13. "_backpackitems","_weapons","_weaponitems","_secweaponitems",
    14. "_handgunitems","_items","_selectWeapon","_isMedic","_isEOD","_memberOnly","_isPublic","_CB_zug","_EF_gruppe","_EF_typ","_EF_name"];
    15. // Skriptbeginn
    16. _display = uiNamespace getVariable ["JGKP_DC_dialog_active", displayNull];
    17. if (isNull _display) exitWith { hint "Display nicht verfügbar!"; };
    18. _CB_zug = _display displayCtrl 2100;
    19. _EF_gruppe = _display displayCtrl 1400;
    20. _EF_typ = _display displayCtrl 1401;
    21. _EF_name = _display displayCtrl 1402;
    22. // Frage Werte für Funktion saveLoadout ab
    23. _zug = _CB_zug lbText (lbCurSel _CB_zug);
    24. _gruppe = ctrlText _EF_gruppe;
    25. _typ = ctrlText _EF_typ;
    26. _name = ctrlText _EF_name;
    27. if (isNil "JGKP_Loadout_Selection") then {
    28. JGKP_Loadout_Selection = [];
    29. JGKP_Loadout_Selection pushBack _gruppe;
    30. JGKP_Loadout_Selection pushBack _typ;
    31. JGKP_Loadout_Selection pushBack _name;
    32. } else {
    33. JGKP_Loadout_Selection set [0, _gruppe];
    34. JGKP_Loadout_Selection set [1, _typ];
    35. JGKP_Loadout_Selection set [2, _name];
    36. };
    37. _unit = player;
    38. _isMedic = DB_san;
    39. _isEOD = DB_eod;
    40. _memberOnly = DB_member;
    41. _isPublic = DB_public;
    42. // Skriptbeginn
    43. _headgear = headgear _unit;
    44. _goggles = goggles _unit;
    45. //// WESTE ////
    46. _vest = vest _unit;
    47. _vestitems = [];
    48. _count = [];
    49. _tempItems = vestItems _unit;
    50. // zählen
    51. {
    52. if !(_x in _vestitems) then {
    53. _vestitems pushBack _x;
    54. _item = _x;
    55. _count pushback ({_x == _item} count _tempItems);
    56. };
    57. } foreach _tempItems;
    58. // DB-Format
    59. {
    60. _vestitems set [_forEachIndex, format["%1~%2;",_x,_count select _forEachIndex]];
    61. } foreach _vestitems;
    62. _vestitems = _vestitems joinString "";
    63. //// UNIFORM ////
    64. _uniform = uniform _unit;
    65. _uniformitems = [];
    66. _count = [];
    67. _tempItems = uniformItems _unit;
    68. // zählen
    69. {
    70. if !(_x in _uniformitems) then {
    71. _uniformitems pushBack _x;
    72. _item = _x;
    73. _count pushBack ({_x == _item} count _tempItems);
    74. };
    75. } foreach _tempItems;
    76. // DB-Format
    77. {
    78. _uniformitems set [_forEachIndex, format["%1~%2;",_x,_count select _forEachIndex]];
    79. } foreach _uniformitems;
    80. _uniformitems = _uniformitems joinString "";
    81. //// RUCKSCK ////
    82. _backpack = backpack _unit;
    83. _backpackitems = [];
    84. _count = [];
    85. // zählen
    86. _tempItems = backpackItems _unit;
    87. {
    88. if !(_x in _backpackitems) then {
    89. _backpackitems pushBack _x;
    90. _item = _x;
    91. _count pushback ({_x == _item} count _tempItems);
    92. };
    93. } foreach _tempItems;
    94. // DB-Format
    95. {
    96. _backpackitems set [_forEachIndex, format["%1~%2;",_x,_count select _forEachIndex]];
    97. } foreach _backpackitems;
    98. _backpackitems = _backpackitems joinString "";
    99. //// WEAPONS ////
    100. _selectWeapon = primaryWeapon _unit;
    101. /*
    102. [
    103. [ //=PRIMARY=
    104. "arifle_MX_ACO_pointer_F", //weapon
    105. "muzzle_snds_H", //suppressor
    106. "acc_pointer_IR", //laser
    107. "optic_Aco", //optics
    108. [ //loaded magazine
    109. "30Rnd_65x39_caseless_mag", //mag type
    110. 30 //mag ammo count
    111. ],
    112. "bipod_01_F_blk" //bipod
    113. ],
    114. [ //=SECONDARY=
    115. "launch_NLAW_F",
    116. "",
    117. "",
    118. "",
    119. [
    120. "NLAW_F",
    121. 1
    122. ],
    123. ""
    124. ],
    125. [ //=HANDGUN=
    126. "hgun_P07_F",
    127. "muzzle_snds_L",
    128. "",
    129. "",
    130. [
    131. "16Rnd_9x21_Mag",
    132. 11
    133. ],
    134. ""
    135. ]
    136. ]
    137. */
    138. _query = weaponsItems _unit;
    139. _weapons = [];
    140. {
    141. _weapons pushBack (_x select 0);
    142. } foreach _query;
    143. _weaponitems = [];
    144. {
    145. // wenn classname mit primary weapon übereinstimmt...
    146. if (_x select 0 == primaryWeapon _unit) then {
    147. _items = _x;
    148. // gehe durch alle items
    149. {
    150. if (_forEachIndex != 0) then {
    151. _item = _x;
    152. if (_forEachIndex == 4 && count _item > 0) then {
    153. _item = _item select 0; // magazin
    154. };
    155. if (count _item != 0) then {
    156. _weaponitems pushBack _item;
    157. };
    158. };
    159. } foreach _items;
    160. }
    161. } foreach _query; // nur Hauptwaffe
    162. _secweaponitems = [];
    163. {
    164. // wenn classname mit primary weapon übereinstimmt...
    165. if (_x select 0 == secondaryWeapon _unit) then {
    166. _items = _x;
    167. // gehe durch alle items
    168. {
    169. if (_forEachIndex != 0) then {
    170. _item = _x;
    171. if (_forEachIndex == 4 && count _item > 0) then {
    172. _item = _item select 0; // magazin
    173. };
    174. if (count _item != 0) then {
    175. _secweaponitems pushBack _item;
    176. };
    177. };
    178. } foreach _items;
    179. }
    180. } foreach _query; // nur Zweitwaffe
    181. _handgunitems = [];
    182. {
    183. // wenn classname mit primary weapon übereinstimmt...
    184. if (_x select 0 == handgunWeapon _unit) then {
    185. _items = _x;
    186. // gehe durch alle items
    187. {
    188. if (_forEachIndex != 0) then {
    189. _item = _x;
    190. if (_forEachIndex == 4 && count _item > 0) then {
    191. _item = _item select 0; // magazin
    192. };
    193. if (count _item != 0) then {
    194. _handgunitems pushBack _item;
    195. };
    196. };
    197. } foreach _items;
    198. }
    199. } foreach _query; // nur Handfeuerwaffe
    200. _weapons = _weapons joinString ";";
    201. _weaponitems = _weaponitems joinString ";";
    202. _secweaponitems = _secweaponitems joinString ";";
    203. _handgunitems = _handgunitems joinString ";";
    204. _items = (assignedItems _unit) joinString ";";
    205. // Server-Funktionsaufruf
    206. ["jgkp_save_loadout",[_unit,_zug,_gruppe,_typ,_name,_headgear,
    207. _goggles,_vest,_vestitems,_uniform,_uniformitems,_backpack,
    208. _backpackitems,_weapons,_weaponitems,_secweaponitems,
    209. _handgunitems,_items,_selectWeapon,_isMedic,_isEOD,_memberOnly,_isPublic]] call CBA_fnc_serverEvent;
    210. // Rückmeldung
    211. waitUntil {
    212. !isNil "ResultSaveLoadout";
    213. };
    214. if ((ResultSaveLoadout select 0) == 1) then {
    215. hint "Loadout erfolgreich gespeichert";
    216. // log-Eintrag in DB log anlegen
    217. ["jgkp_log_action", [getPlayerUID _unit,"loadout",format ["[add,%1,%2,%3,%4,%5]",(ResultSaveLoadout select 1) select 0,_zug,_gruppe,_typ,_name]]] call CBA_fnc_serverEvent;
    218. } else {
    219. hint "Es trat ein Fehler beim Speichern auf.\nBitte Administrator kontaktieren.";
    220. };
    221. ResultSaveLoadout = nil;
    Alles anzeigen




    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:

    Quellcode

    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:

    Spoiler anzeigen

    C-Quellcode: jgkp_save_loadout

    1. ["jgkp_save_loadout", {
    2. /*
    3. ["jgkp_save_loadout",[unit,zug,gruppe,typ,name,headgear,
    4. goggles,vest,vestitems,uniform,uniformitems,backpack,
    5. backpackitems,weapons,weaponitems,secweaponitems,
    6. handgunitems,items,selectWeapon,isMedic,isEOD,memberOnly, isPublic]] call CBA_fnc_serverEvent;
    7. Speichert den Inhalt des aktuell ausgerüsteten Loadouts in der DB.
    8. Kein Rückgabewert außer bei Fehlern.
    9. */
    10. JGKP_var_startParams = _this;
    11. (_this) spawn {
    12. params ["_unit", "_zug", "_gruppe", "_typ", "_name", "_headgear", "_goggles", "_vest", "_vestitems", "_uniform", "_uniformitems", "_backpack", "_backpackitems", "_weapons", "_weaponitems", "_secweaponitems", "_handgunitems", "_items", "_selectWeapon", "_isMedic", "_isEOD", "_memberOnly", "_isPublic"];
    13. _owner = owner _unit;
    14. //busy wait
    15. if (not isNil "serverRunningQuery") then {
    16. waitUntil{ not serverRunningQuery}
    17. };
    18. serverRunningQuery = true;
    19. _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",
    20. _zug,_gruppe,_typ,_name,_headgear,_goggles,_vest,_vestitems,_uniform,_uniformitems,_backpack,_backpackitems,_weapons, _weaponitems,_secweaponitems,_handgunitems,_items,_selectWeapon,_isMedic,_isEOD,_memberOnly,_isPublic]);
    21. ResultSaveLoadout = _query;
    22. _owner publicVariableClient "ResultSaveLoadout";
    23. serverRunningQuery = false;
    24. };
    25. }] call CBA_fnc_addEventHandler;
    Alles anzeigen




    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.
    Kompaniechef der 3.Jägerkompanie
    Autor des SQF-Scriptquide
  • 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.