[jTuT] Fraktions-Fahrzeug-System mit MySQL R41-4
Hallo zusammen,
da mir in letzter Zeit die Frage nach einem Fraktions-Fahrzeug-System basierend auf dem aktuellen (Stand 12/2019) MySQL R41-4 Plugin häufig aufgefallen ist, und ich kein vernünftiges Tutorial gefunden habe, poste ich jetzt selbst eines. Gerne kann dieser Thread auch für Fragen bezüglich dem System oder eventuellen Änderungen verwendet werden.
Voraussetzungen:
- Einzigste Voraussetzung für dieses Tutorial ist ein vorhandenes Fraktionssystem
Kurzer Überblick:
- Dieses Tutorial beschreibt und erklärt die Erstellung von Fahrzeugen, die nur von einer bestimmten Fraktion verwendet werden können. Außerdem wird der Aufbau der Tabelle in der Datenbank, sowie das Laden und das Speichern der Fahrzeuge erklärt. Am Ende haben wir ein komplett funktionsfähiges Fraktions-Fahrzeug-System welches auf BlueG's MySQL Plugin Version R41-4 basiert. Falls Du das Plugin noch nicht hast, lade es Dir bitte jetzt herunter und füge es entsprechend in Deinen Server ein.
Es ist zu beachten, dass die Codes ganz bewusst mit englischen Variablennamen versehen worden sind, da dies der Einheitlichkeit und Allgemeinverständlichkeit eines Codes dient. Kommentare habe ich auf deutsch gehalten.
Wir arbeiten uns durch den Code:
- Zuerst müssen wir uns überlegen, welche Daten wir in der Datenbank speichern möchten. Bei diesem Fahrzeug-System nehmen wir der Einfachheit halber die gängingsten Daten, die da wären:
Model-ID, X, Y, Z, Rotation, Farbe1, Farbe2 und die FraktionsID
Außerdem benötigen wir eine Spalte ID die als Primary Key definiert wird.
Natürlich können weitere Daten beliebig hinzugefügt werden. Damit das Ganze auch ohne Probleme funktioniert, selbst wenn die Tabelle (aus Versehen?) gelöscht wird, erstellen wir die Tabelle automatisch über den Code. Hierzu verwenden wir die folgende Funktion:C
Alles anzeigenCreateFVTable() { new query[512]; format(query, sizeof(query), "CREATE TABLE IF NOT EXISTS `FracVehicles` ("); format(query, sizeof(query), "%s`ID` int(11) NOT NULL PRIMARY KEY,", query); format(query, sizeof(query), "%s `ModelID` int(11) NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `PosX` float NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `PosY` float NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `PosZ` float NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `PosA` float NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `Color1` int(11) NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `Color2` int(11) NOT NULL DEFAULT '0',", query); format(query, sizeof(query), "%s `FracID` int(11) NOT NULL DEFAULT '-1'", query); format(query, sizeof(query), "%s) ENGINE=InnoDB DEFAULT CHARSET=latin1;", query); //handle = die Connection Handle ID die vor mysql_connect steht. mysql_tquery(handle, query); return 1; }
Die Tabelle sieht dann in der Datenbank so aus:
(Quelle: Eigener Screenshot - phpMyAdmin) - Dazu erstellen wir auch gleich noch die später benötigte Funktion LoadFracVehicles(), in diese packen wir die obige Funktion:
Beide Funktionen musst Du ganz unten in Deinem Gamemode einfügen.
Unter OnGameModeInit (bzw. OnFilterScriptInit) musst Du dann folgendes einfügen: - Startest Du jetzt den Server, dann wird die Tabelle direkt in der Datenbank erstellt (falls sie noch nicht existiert), natürlich ohne Daten.
Jetzt benötigen wir noch einen Speicherplatz für die später erstellten Fahrzeuge, das machen wir mit einem Enum und einer zweidimensionalen Variable. Dazu benötigen wir noch einen Maximal-Wert, den wir hier mit 10 definieren, das kann aber beliebig geändert werden (beachte das Limit an maximal erstellbaren Fahrzeugen (MAX_VEHICLES)).C
Alles anzeigen#define MAX_FRAC_VEHICLES 10 enum fracVehEnum { e_modelID, Float:e_x, Float:e_y, Float:e_z, Float:e_a, e_color1, e_color2, e_fracID, e_vID }; new fracVehicle[MAX_FRAC_VEHICLES][fracVehEnum];
Daten können entweder direkt in die Datenbank eingetragen werden, oder mit den Befehlen erstellt/gelöscht werden, die wir später zusammen erstellen.
In der Datenbank sieht das dann später zum Beispiel so aus:
(Quelle: Eigener Screenshot - phpMyAdmin) - Jetzt nehmen wir uns die Funktion von vorhin wieder her und erweitern sie um die Funktion Daten aus der neuen Tabelle zu laden.
Wir bauen dort nun eine Anweisung ein, die uns die Daten aus der Datenbank holt:
CLoadFracVehicles() { CreateFVTable(); //handle = die Connection Handle ID die vor mysql_connect steht. //Es wird ein SELECT Query an die Datenbank gesendet, diese gibt über das Callback LoadFracVehicles_Data //die Daten im Cache zurück. mysql_tquery(handle, "SELECT * FROM FracVehicles ORDER BY ID ASC", "LoadFracVehicles_Data"); return 1; }
- Dazu benötigen wir natürlich das Callback, in welchem wir dann die Daten aus dem Cache auslesen:C
Alles anzeigenforward LoadFracVehicles_Data(); public LoadFracVehicles_Data() { //Deklaration neuer temporärer Variablen: new rows, id, createdVehs; //Anzahl der gelesenen Datensätze abfragen: cache_get_row_count(rows); //Schleife durch alle Datensätze: for(new i=0; i<rows; i++) { //Fahrzeug existiert in der Datenbank, wir laden die Daten jetzt aus dem Cache. cache_get_value_name_int(i, "ID", id); //Um Fehler bei der Verarbeitung zu vermeiden: if(id < 0 || id >= MAX_FRAC_VEHICLES) continue; //Danach die restlichen Daten laden. Unser Index (der Wert mit dem wir auf das //Fahrzeug in der Datenbank zugreifen) ist die "ID". cache_get_value_name_int(i, "ModelID", fracVehicle[id][e_modelID]); cache_get_value_name_float(i, "PosX", fracVehicle[id][e_x]); cache_get_value_name_float(i, "PosY", fracVehicle[id][e_y]); cache_get_value_name_float(i, "PosZ", fracVehicle[id][e_z]); cache_get_value_name_float(i, "PosA", fracVehicle[id][e_a]); cache_get_value_name_int(i, "Color1", fracVehicle[id][e_color1]); cache_get_value_name_int(i, "Color2", fracVehicle[id][e_color2]); cache_get_value_name_int(i, "FracID", fracVehicle[id][e_fracID]); //Zur Sicherheit fragen wir hier nun ab, ob das angegebene Modell auch existiert, //sollte das nicht der Fall sein, dann wird das Fahrzeug nicht erstellt. if(fracVehicle[id][e_modelID] >= 400 && fracVehicle[id][e_modelID] <= 611) { //Nachdem das Fahrzeug geladen wurde, wird es erstellt. fracVehicle[id][e_vID] = CreateVehicle(fracVehicle[id][e_modelID], fracVehicle[id][e_x], fracVehicle[id][e_y], fracVehicle[id][e_z], fracVehicle[id][e_a], fracVehicle[id][e_color1], fracVehicle[id][e_color2], -1); } else { //Falls eine nicht existierende Model-ID angegeben wurde, geben wir eine Information aus. printf("[Error] Model-ID %d existiert nicht (ID: %d).", fracVehicle[id][e_modelID], id); } //Zur Sicherheit fragen wir noch ab, ob das Fahrzeug-Limit noch nicht überschritten wurde. //Existiert ein Fahrzeug mit der höchsten ID, dann kann kein weiteres mehr erstellt werden. if(GetVehicleModel(MAX_VEHICLES-1) != 0) return print("Es können keine weiteren Fahrzeuge geladen werden, Limit (MAX_VEHICLES) erreicht."); //Zur weiteren Sicherheit prüfen wir noch, ob die maximale Anzahl an Fraktions-Fahrzeugen erreicht ist. createdVehs++; if(createdVehs == MAX_FRAC_VEHICLES) return print("Es können keine weiteren Fahrzeuge geladen werden, Limit (MAX_FRAC_VEHICLES) erreicht."); } return 1; }</rows;>
Damit hätten wir das Laden erledigt. Dieses Callback muss ebenfalls ganz unten im Gamemode eingefügt werden.
- Unser Fahrzeug wird nun also erstellt, falls es in der Datenbank existiert. Jetzt müssen wir die Daten natürlich nach einer eventuellen Änderung speichern lassen. Dazu eignet sich zum Beispiel ein Timer, der jede Minute alle Fahrzeuge speichert.
Dazu erstellen wir bei OnGameModeInit einen Timer:Und ganz unten im Gamemode erstellen wir wieder das Callback:
C
Alles anzeigenforward SaveAllFracVehs(); public SaveAllFracVehs() { //Deklaration neuer temporärer Variablen: new query[256]; //Schleife durch alle Fraktions-Fahrzeuge for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Wenn das Fahrzeug existiert if(fracVehicle[i][e_modelID] >= 400 && fracVehicle[i][e_modelID] <= 611) { //Wir fragen die Position des Fahrzeugs ab: GetVehiclePos(fracVehicle[i][e_vID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z]); GetVehicleZAngle(fracVehicle[i][e_vID], fracVehicle[i][e_a]); //Und wir erstellen das Query: format(query, sizeof(query), "UPDATE FracVehicles SET ModelID = '%d', PosX = '%f', PosY = '%f', PosZ = '%f', PosA = '%f', Color1 = '%d', Color2 = '%d', FracID = '%d' WHERE ID = '%d'", fracVehicle[i][e_modelID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z], fracVehicle[i][e_a], fracVehicle[i][e_color1], fracVehicle[i][e_color2], fracVehicle[i][e_fracID], i); //handle = die Connection Handle ID die vor mysql_connect steht. mysql_tquery(handle, query); } } return 1; }
Diese Funktion kann natürlich auch bei OnGameModeExit eingefügt werden, falls gewünscht.
Dazu einfachverwenden.
Hinweis: Je nach dem wie viele Fahrzeuge erstellt werden macht es mehr oder weniger Sinn einen Timer zu verwenden. Bei 100 Fahrzeugen ist das kein Problem, hat man aber zum Beispiel 1000 Fahrzeuge, dann kann aufgrund der 1000 gleichzeitig ausgeführten Queries Lag entstehen, bzw. der Server kurzzeitig eingefroren sein. Dann sollte man von dieser Methode abstand nehmen und nur auf die nachfolgende Methode zurückgreifen.
- Wenn wir schon dabei sind, dann können wir auch gleich noch eine Funktion erstellen, die ein Fahrzeug anhand seiner Vehicle ID speichern kann, sofern es ein Fraktions-Fahrzeug ist. Diese Funktion sieht dann so aus:C
Alles anzeigenstock SaveFracVehCheck(vehicleid) { //Deklaration neuer temporärer Variablen: new query[256]; //Schleife durch alle Fraktions-Fahrzeuge for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Wenn die vehicleid die gleiche ist, wie die des Fraktions-Fahrzeugs if(fracVehicle[i][e_vID] == vehicleid) { //Wir fragen die Position des Fahrzeugs ab: GetVehiclePos(fracVehicle[i][e_vID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z]); GetVehicleZAngle(fracVehicle[i][e_vID], fracVehicle[i][e_a]); //Und wir erstellen das Query: format(query, sizeof(query), "UPDATE FracVehicles SET ModelID = '%d', PosX = '%f', PosY = '%f', PosZ = '%f', PosA = '%f', Color1 = '%d', Color2 = '%d', FracID = '%d' WHERE ID = '%d'", fracVehicle[i][e_modelID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z], fracVehicle[i][e_a], fracVehicle[i][e_color1], fracVehicle[i][e_color2], fracVehicle[i][e_fracID], i); //handle = die Connection Handle ID die vor mysql_connect steht. mysql_tquery(handle, query); return 1; //Es gibt ja keine zwei gleichen Fahrzeuge } } return 0; //Fahrzeug ist kein Fraktions-Fahrzeug }
Das können wir, wenn wir es schon haben, bei OnPlayerExitVehicle einbauen, damit das Fahrzeug gespeichert wird, wenn ein Spieler es verlässt.
Dazu einfach bei OnPlayerExitVehicle das einfügen:Alternativ kann man auch einen /parken Befehl erstellen, durch den man die neue Position speichert.
Das könnte dann so aussehen:Cnew vehicleid = GetPlayerVehicleID(playerid); if(SaveFracVehCheck(vehicleid)) return SendClientMessage(playerid, 0x00FF00FF, "Fahrzeug geparkt."); else return SendClientMessage(playerid, 0xFF0000FF, "Dieses Fahrzeug kann nicht geparkt werden.");
Je nach Server ist das eine oder das andere sinnvoll. Man kann es aber auch ganz anders machen, das bleibt jedem selbst überlassen.
- Falls sich die Farben der Fahrzeuge ändern können, dann müssen an entsprechender Stelle auch die Werte der Variablen geändert werden.
Dies macht man beispielsweise so:C
Alles anzeigen//Eine Schleife durch alle Fraktions-Fahrzeuge for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Wenn die vehicleid die gleiche ist, wie die des Fraktions-Fahrzeugs if(fracVehicle[i][e_vID] == vehicleid) { //Dann ändere die Farbe. fracVehicle[i][e_color1] = color1; fracVehicle[i][e_color1] = color2; break; //Es gibt ja keine zwei gleichen Fahrzeuge } }
Je nach dem an welcher Stelle das bei Dir im Code relevant ist, das können viele Stellen sein, muss aber nicht.
Damit hätten wir den größten Teil eigentlich auch schon hinter uns. Es fehlt nur noch eine Kleinigkeit. - In diesem Tutorial geht es ja darum, Fraktions-Fahrzeuge zu erstellen. Momentan kann unsere Fahrzeuge aber noch jeder betreten, daher müssen wir jetzt bei OnPlayerEnterVehicle noch folgendes hinzufügen:C
Alles anzeigen//Eine Schleife durch alle Fraktions-Fahrzeuge for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Wenn die vehicleid die gleiche ist, wie die des Fraktions-Fahrzeugs if(fracVehicle[i][e_vID] == vehicleid) { //Jetzt brauchen wir die Variable, mit der Dein vorher bereits existierendes Fraktions-System arbeitet. //Ich nehme jetzt einfach eine Beispiel-Variable, diese musst Du selbst mit der ersetzen, //die Dein System verwendet. //Es geht um diese Variable: PlayerInfo[playerid][pFraction] if(PlayerInfo[playerid][pFraction] != fracVehicle[i][e_fracID]) { //Wenn der Spieler nicht in der Fraktion ist, die das Fahrzeug verwenden darf, //dann darf er es nicht betreten. TogglePlayerControllable(playerid, false); TogglePlayerControllable(playerid, true); SendClientMessage(playerid, 0xFF0000FF, "Du bist nicht in der Fraktion, die dieses Fahrzeug verwenden darf."); } break; //Es gibt ja keine zwei gleichen Fahrzeuge } }
Wie gesagt, dieser Code muss bei OnPlayerEnterVehicle eingefügt werden.
- Jetzt könnten wir eigentlich den Server starten und alles funktioniert einwandfrei. Allerdings müssen wir die Daten für die Fraktions-Fahrzeuge immer mühselig manuell in die Datenbank eintragen, und das macht ja recht wenig Sinn. Deshalb erstellen wir jetzt noch einen Befehl um Fraktions-Fahrzeuge im Spiel zu erstellen und einen Befehl um Fraktions-Fahrzeuge zu löschen. Im Beispiel nutze ich ocmd. Falls Du einen anderen verwendest kannst du dies einfach umschreiben.
Außerdem verwende ich in den Beispielen strtok, da dies ohne Plugin realsierbar ist und somit eine höhere Funktionalität gewährleistet.
Die beiden Funktionen:C
Alles anzeigenstock strtok(const string[], &index) //©Jeffry { new result[20], length = strlen(string), i = index; while ((i < length) && (string[i] == ' ')) i++; strmid(result,string,i,((index = strfind(string, " ", false, i)) == -1) ? (index = length) : (index) , 20); index++; return result; } stock IsNumeric(string[]) { for (new i = 0, j = strlen(string); i < j; i++) { if ((string[i] > '9' || string[i] < '0')) return 0; } return 1; }
Fraktions-Fahrzeuge erstellen:
C
Alles anzeigenocmd:fferstellen(playerid, params[]) { //Zuerst fragen wir ab, ob wir noch Fahrzeuge erstellen können if(GetVehicleModel(MAX_VEHICLES-1) != 0) return SendClientMessage(playerid, 0xFF0000FF, "Es können keine weiteren Fahrzeuge erstellt werden, Limit (MAX_VEHICLES) erreicht."); //Wir deklarieren die benötigten temporären Variablen. new tmp[20], idx, model, fID, color1 = -1, color2 = -1; //Anschließend teilen wir mit strtok die eingegebenen Paramater auf und wandeln sie in eine Zahl um. //Falls etwas falsches eingegeben wurde, geben wir eine entsprechende Meldung zurück. tmp = strtok(params, idx); if(!strlen(tmp) || !IsNumeric(tmp)) return SendClientMessage(playerid, 0xFF0000FF, "Verwendung: /fferstellen [Model] [FraktionsID] [opt:Farbe1] [opt:Farbe2]"); model = strval(tmp); //Eine solche Abfrage kann man auch für nachfolgende FraktionsID (fID) machen, falls gewünscht. if(model < 400 || model > 611) return SendClientMessage(playerid, 0xFF0000FF, "Error: Model ID existiert nicht."); tmp = strtok(params, idx); if(!strlen(tmp) || !IsNumeric(tmp)) return SendClientMessage(playerid, 0xFF0000FF, "Verwendung: /fferstellen [Model] [FraktionsID] [opt:Farbe1] [opt:Farbe2]"); fID = strval(tmp); tmp = strtok(params, idx); if(strlen(tmp) && IsNumeric(tmp)) color1 = strval(tmp); tmp = strtok(params, idx); if(strlen(tmp) && IsNumeric(tmp)) color2 = strval(tmp); //Sind alle Parameter korrekt übergeben worden, dann suchen wir uns einen freien Index in den wir das Fraktions-Fahrzeug speichern können. for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Frei: if(fracVehicle[i][e_vID] == 0) { //Daten werden übergeben new Float:x, Float:y, Float:z, Float:a; GetPlayerPos(playerid, x, y, z); GetPlayerFacingAngle(playerid, a); fracVehicle[i][e_modelID] = model; fracVehicle[i][e_x] = x; fracVehicle[i][e_y] = y; fracVehicle[i][e_z] = z; fracVehicle[i][e_a] = a; fracVehicle[i][e_color1] = color1; fracVehicle[i][e_color2] = color2; fracVehicle[i][e_fracID] = fID; //und anschließend in die Datenbank geschrieben. new query[256]; format(query, sizeof(query), "INSERT INTO FracVehicles (ID, ModelID, PosX, PosY, PosZ, PosA, Color1, Color2, FracID) VALUES ('%d', '%d', '%f', '%f', '%f', '%f', '%d', '%d', '%d')", i, fracVehicle[i][e_modelID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z], fracVehicle[i][e_a], fracVehicle[i][e_color1], fracVehicle[i][e_color2], fracVehicle[i][e_fracID]); mysql_tquery(handle, query); //Zuletzt wird dann noch das Fahrzeug erstellt und der Spieler in das Fahrzeug gesetzt. fracVehicle[i][e_vID] = CreateVehicle(fracVehicle[i][e_modelID], fracVehicle[i][e_x], fracVehicle[i][e_y], fracVehicle[i][e_z], fracVehicle[i][e_a], fracVehicle[i][e_color1], fracVehicle[i][e_color2], -1); PutPlayerInVehicle(playerid, fracVehicle[i][e_vID], 0); return SendClientMessage(playerid, 0x00FF00FF, "Fahrzeug gespeichert."); } } return SendClientMessage(playerid, 0xFF0000FF, "Error: Limit für Fraktions-Fahrzeuge erreicht."); }
Fraktions-Fahrzeuge löschen:
C
Alles anzeigenocmd:ffloeschen(playerid, params[]) { //Wir deklarieren die benötigte temporäre Variable. new vid; //Wenn der Spieler keine Vehicle-ID eingibt wird die gelöscht, in der sich der Spieler befindet, //ansonsten die eingegebene ID. if(!IsNumeric(params) || !strlen(params)) return SendClientMessage(playerid, 0xFF0000FF, "Error: Nutze: /ffloeschen [Vehicle ID]"); vid = strval(params); if(vid <= 0) return SendClientMessage(playerid, 0xFF0000FF, "Error: Vehicle-ID ist kein Fraktions-Fahrzeug."); for(new i=0; i<MAX_FRAC_VEHICLES; i++) { //Eingegebene ID und die des Fraktions-Fahrzeugs stimmen überein. if(fracVehicle[i][e_vID] == vid) { //Daten werden entfernt fracVehicle[i][e_modelID] = 0; fracVehicle[i][e_x] = 0.0; fracVehicle[i][e_y] = 0.0; fracVehicle[i][e_z] = 0.0; fracVehicle[i][e_a] = 0.0; fracVehicle[i][e_color1] = 0; fracVehicle[i][e_color2] = 0; fracVehicle[i][e_fracID] = -1; //und anschließend in der Datenbank gelöscht. new query[128]; format(query, sizeof(query), "DELETE FROM FracVehicles WHERE ID = '%d'", i); mysql_tquery(handle, query); DestroyVehicle(fracVehicle[i][e_vID]); fracVehicle[i][e_vID] = 0; return SendClientMessage(playerid, 0x00FF00FF, "Fahrzeug gelöscht."); } } return SendClientMessage(playerid, 0xFF0000FF, "Error: Vehicle-ID ist kein Fraktions-Fahrzeug."); }
Und wenn wir schon dabei sind machen wir auch gleich noch den /parken Befehl komplett:
Cocmd:parken(playerid) { if(!IsPlayerInAnyVehicle(playerid)) return SendClientMessage(playerid, 0xFF0000FF, "Du bist in keinem Fahrzeug."); new vehicleid = GetPlayerVehicleID(playerid); if(SaveFracVehCheck(vehicleid)) return SendClientMessage(playerid, 0x00FF00FF, "Fahrzeug geparkt."); else return SendClientMessage(playerid, 0xFF0000FF, "Dieses Fahrzeug kann nicht geparkt werden."); }
- Damit wären wir durch. Es sind alle notwendigen Teile im Code vorhanden.
Ich hoffe, das System funktioniert bei Dir jetzt wie bei mir ohne Probleme. Wenn nicht, oder wenn irgendwelche Fragen auftreten, bzw. aufgetreten sind, darfst Du diese gerne hier oder in der Scripting Base stellen.
Für die Faulen unter uns:
Natürlich kann man das System weiter ausbauen, und weitere Daten hinzufügen, je nach dem was für den jeweiligen Server passend ist. Das könnten gefahrene Kilometer, Nummerntafeln, Tunings, etc... sein, da gibt es sehr viele Möglichkeiten. Diese kann man alle zimelich einfach hinzufügen. Falls Probleme auftreten, wie gesagt, einfach fragen!
Viel Spaß damit!
Beste Grüße,
Jeffry