Dynamisches Checkpoint-Haussystem mit 3DText | Streamer & MySQL R7 (Cached)

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

  • So, heute machen wir uns an ein dynamisches Haussystem mit den folgenden Eigenschaften:

    • Unter Verwendung von gestreamten Checkpoints, das heißt, dass jeweils nur ein Checkpoint zu sehen ist und ebenfalls dieser nur in unmittelbarer Nähe. Daher wäre diese Art von Eingang vorallem für Roleplay-bedachte Server geeignet.
    • Ebenfalls werden gestreamte 3DText-Label verwendet, welche in die Mitte des Checkpoints gesetzt werden. Sie sind ebenfalls nur in unmittelbarer Nähe zu sehen, sodass in Gegenden mit vielen Häusern das Haussystem weiterhin gut zu betrachten ist.
    • Um die Daten der Häuser zu speichern verwenden wir BlueG's MySQL R7 Plugin, welches mit Cache-Funktionen eine gute Performance aufweisen kann und ebenfalls sehr gut zu bedienen ist. Vorallem bei einem Haussystem kann MySQL sehr viele Vorteile bringen, sobald man sich ein größeres Haussystem mit mehreren kleinen Systemen erstellen möchte.
    • Da wir MySQL verwenden werden ist natürlich eine Datenbank benötigt. Um lokal zu programmieren verwende ich selbst meistens XAMPP, wodurch mir direkt nach der Installation alle nötigen Funktionen zur Verfügung stehen, um Datenbanken reichlich zu erstellen. Darauf werde ich jedoch nicht weiter eingehen, da XAMPP durchaus des öfteren schon erwähnt und erklärt wurde. Das einzigst hilfreiche zu erwähnen wäre, dass man durch einen Klick auf 'Admin' unter 'MySQL' im XAMPP Control-Panel direkt zu phpMyAdmin gelangt.
    • Wie bereits angesprochen werden wir 3DText-Label und Checkpoints streamen. Wir werden demnach den Streamer von Incognito benutzen, da dieser einfach zu bedienen ist und sehr etabliert ist.
    • Um Befehle für das Haussystem zu erstellen werde ich im Tutorial ZCMD verwenden, da ZCMD für mich als eine hilfreiche Erweiterung erscheint und sehr einfach für euch ist, um es auf andere Scriptingarten umzuschreiben.
    • Zur Handhabung des Splitten von Parametern bei Befehlseingabe werde ich schlussendlich sscanf von Y_Less zur Hand nehmen, da es ebenfalls bereits sehr etabliert ist.


    Unser Scriptkopf sieht demnach also nun wie folgt aus:



    #include <a_samp> // SA:MP Include
    #include <a_mysql> // MySQL R7
    #include <streamer> // Streamer
    #include <sscanf2> // sscanf
    #include <zcmd> // ZCMD


    main() {}


    Links zu den benötigten Plugins & Includes:


    Inhalt des Tutorials:

    • Planung & Umsetzung der MySQL Datenbank
    • Auslesen der MySQL Datenbank
    • Wichtigste Grundbefehle
    • Abschließendes


    Planung & Umsetzung der MySQL Datenbank


    Zu Beginn des Tutorials möchte ich anmerken, dass dieses Tutorial lediglich den Grundbaustein für ein ausgereiftes Haussystem legt. Es soll dazu dienen, eine Art der Realisierung eines Haussystems mit den genannten Hilfsmitteln darzulegen. Dies ist daher weder eine perfekte noch eine ausgereifte Version, sondern ein Grundscript, welches in ca. 1h Arbeit entstand. Anzumerken ist ebenfalls, dass jedes größere System etwas an Planung benötigt. Die grundlegende Planung unseres Systems haben wir durch das Festlegen der Plugins & Includes bereits getan. Gehen wir also genauer auf die Planung der MySQL Datenbank ein - es sollte folgende Eigenschaft erfüllt sein:

    • Die Datenbank sollte über einen eindeutigen Wert verfügen, wodurch ein Haus eindeutig identifiziertbar ist. Dieser Wert sollte jedoch nicht vom Script aus kontrolliert werden, sondern von der Datenbank selbst, da sonst die Gefahr eines Fehlers zu hoch läge. Abhilfe kann uns MySQL also durch die Spalteneigenschaften 'PRIMARY_KEY' und 'AUTO_INCREMENT' leisten. 'PRIMARY_KEY' wird wie ein 'UNIQUE_KEY' angesehen, da dieser eindeutig ist und als Index der Tabelle fungiert. 'AUTO_INCREMENT' nimmt uns die Arbeit des Zählens ab, da es automatisch neue Datensätze mit einem neuen eindeutigen Index verseht.

    Es muss also der Index auf 'PRIMARY' gesetzt werden und 'AUTO_INCREMENT' aktiviert werden - Wir verwenden eine ganze Zahl, also einen Integer (INT) (Bild: http://www.abload.de/img/database13qw5.jpg).


    Um unsere Daten in der Datenbank zu speichern, müssen wir uns zuerst überlegen, welche Spalten wir in der Tabelle, in welcher wir unsere Häuser speicher werden, benötigen. Wir arbeiten also an unserem Script weiter, indem wir einen Enumerator erstellen und uns überlegen, was zu speichern sei:



    enum hausEnumerator {
    hID, // Eindeutige ID des Hauses (Typ: INT)
    hPreis, // Preis zum Erwerb des Hauses (Typ: INT)
    hBesitzer[MAX_PLAYER_NAME], // Name des Besitzers des Hauses (Typ: VARCHAR ; Größe: MAX_PLAYER_NAME = 24)
    hInterior, // Interior des Hauses (nicht die InteriorID - mehr dazu später!) (Typ: INT)
    Float:hX, // X-Koordinate des Hauseingangs (Typ: FLOAT)
    Float:hY, // Y-Koordinate des Hauseingangs (Typ: FLOAT)
    Float:hZ // Z-Koordinate des Hauseingangs (Typ: FLOAT)
    };

    Die Tabelle sollte also nach folgendem Schema erstellt werden (Dies ist ein Beispiel!):


    SQL
    CREATE TABLE IF NOT EXISTS `breadfish_houses` (
      `hID` int(11) NOT NULL AUTO_INCREMENT,
      `hPreis` int(11) NOT NULL,
      `hBesitzer` varchar(24) NOT NULL,
      `hInterior` int(11) NOT NULL,
      `hX` float NOT NULL,
      `hY` float NOT NULL,
      `hZ` float NOT NULL,
      PRIMARY KEY (`hID`)
    );


    Auslesen der MySQL Datenbank


    Also, kommen wir als nächstes ans eigentliche Scripting, um was es in diesem Tutorial ja eigentlich gehen soll. Fangen wir damit an, uns eine Verbindung zu MySQL aufzubauen, damit wir auf die Werte per Queries zugreifen können:



    #define SQL_DATABASE "database" // Datenbankname
    #define SQL_HOST "localhost" // Hostname
    #define SQL_USER "root" // Username
    #define SQL_PASSWORD "" // Passwort


    new sqlHandle; // Variable zum Zwischenspeichern des MySQL-Handles (Teilweise für Funktionen als Parameter benötigt!).


    public OnGameModeInit() { // Wir bauen eine Verbindung auf, sobald der Gamemode geladen wird.
    // Verbindung mit den oben definierten Parametern aufbauen und Handle übergeben.
    sqlHandle = mysql_connect(SQL_HOST, SQL_USER, SQL_DATABASE, SQL_PASSWORD);

    // Testen, ob eine Verbindung besteht - Falls nein, Fehlermeldung + Exit!
    if(mysql_ping(sqlHandle) != 1) {
    print("MySQL Error: Es konnte keine Verbindung zur Datenbank hergestellt werden.");
    SendRconCommand("exit");
    }
    return 1;
    }


    Eine Verbindung zur Datenbank sollte nun also stehen - ihr könnte ja bereits den Gamemode einmal starten, um zu testen, ob die Console sich wieder schließt und eine Fehlermeldung ausgibt, oder ob die Verbindung besteht. Gehen wir weiter zum Laden den Häuser. Dies werden wir ebenfalls im OnGameModeInit() Callback durchführen, sodass alle Häuser geladen sind, sobald der erste Spieler den Server betritt. Im folgenden werden cache-Funktionen, die in BlueG's MySQL R7 zum ersten mal zur Verfügung stehen, verwendet. Bei Problemen könnt ihr euch auch Tutorials über das Caching anschauen (die gibt es auf english als auch auf deutsch). Wir erweitern den Callback also um eine Funktion, welche unseren ersten Query ausführen soll:



    public OnGameModeInit() { // Wir bauen eine Verbindung auf, sobald der Gamemode geladen wird.
    // Verbindung mit den oben definierten Parametern aufbauen und Handle übergeben.
    sqlHandle = mysql_connect(SQL_HOST, SQL_USER, SQL_DATABASE, SQL_PASSWORD);

    // Testen, ob eine Verbindung besteht - Falls nein, Fehlermeldung + Exit!
    if(mysql_ping(sqlHandle) != 1) {
    print("MySQL Error: Es konnte keine Verbindung zur Datenbank hergestellt werden.");
    SendRconCommand("exit");
    }
    /* Query ausführen, welcher unsere Werte aus der Tabelle holt.
    Parameter: connectionHandle, query[], bool:cache, callback[], format[], {Float, ... }
    Caching aktiviert, da wir Werte auslesen (SELECT) und später übergeben müssen. All dies werden wir in OnGameModeLoadHouses tun.
    */
    mysql_function_query(sqlHandle, "SELECT `hID`, `hPreis`, `hBesitzer`, `hInterior`, `hX`, `hY`, `hZ` FROM `breadfish_houses`", true, "OnGameModeLoadHouses", "", "");
    return 1;
    }

    Zu beachten beim Formulieren von Queries ist:

    • Tabellennamen und Feldnamen mit Backticks (´) schreiben, sodass diese von MySQL-Begriffen unterschieden werden können.
    • Falls -Strings- (keine Zahlen!) in den Query eingebaut werden, diese immer zuvor escapen, um Fehler des Querys zu verhinden (Hilfeleistung durch mysql_format()).
    • * zu verwenden ist bei kleineren Queries akzeptabel, bei größeren jedoch sollte man jedoch die zusätzliche Arbeit MySQL ersparen.
    • Standartmäßig werden MySQL-Anweisungen komplett groß geschrieben.


    Da MySQL R7 alle Queries, die ausgeführt werden, threaded (das heißt auf einem anderen Thread weiterlaufen lässt, um den Programmfluss nicht zu stoppen, bis der Query ein Ergebnis liefert) müssen wir nun den Umweg über diesen Callback gehen, der auf dem 2. Thread laufen wird (Extra Tutorial mit MySQL R6 zum gleichen Sachverhalt: Click here!). Der Grund liegt darin, dass MySQL R7 die Queries in eine Art Warteschlange einreiht und Schritt für Schritt abarbeitet. Dabei läuft der eigentliche Quellcode weiter und wartet nicht auf eine Antwort. Da es so ohne Warten auf Beendigung des Queries dazu kommen kann, dass wir NULL-Werte einlesen, lassen wir den Callback nach Erreichen eines Ergebnisses aufrufen. Daher gehen wir nun wie folgt vor:



    #define MAX_HOUSES (200) // Definieren der Häuserslots - Kann jederzeit erhöht werden, jedoch desto mehr, desto mehr Arbeit für das Script.


    new sqlHandle,
    hausInfo[MAX_HOUSES][hausEnumerator]; // Array, welcher in Verbindung mit dem Enumerator die Daten der Häuser hält.


    forward OnGameModeLoadHouses(); // Wir verfügen über einen public-Callback - dieser muss geforwarded werden -vor- Gebrauch der Funktion.


    public OnGameModeLoadHouses() {
    new rows, fields, content[MAX_PLAYER_NAME]; // Deklaration der benötigten Variablen (content muss max. so groß wie ein Username sein).
    cache_get_data(rows, fields); // Die Anzahl der Reihen und Spalten der Ergebnismenge herauslesen und abspeichern.


    for(new i = 0; i != rows; i++) { // Schleife die Anzahl der Reihen (= Anzahl der Häuser) durchlaufen lassen.
    cache_get_row(i, 0, content); // Daten einer Zeile im String 'content' speichern.
    hausInfo[i][hID] = strval(content); // HausArray mit Enumerator mit dem Integer (strval()) füllen.
    cache_get_row(i, 1, content); // Wiederholung für andere Werte ...
    hausInfo[i][hPreis] = strval(content);
    cache_get_row(i, 2, content); // Feld steigt nach und nach wie auch im Query von links nach rechts.
    format(hausInfo[i][hBesitzer], MAX_PLAYER_NAME, "%s", content);
    cache_get_row(i, 3, content);
    hausInfo[i][hInterior] = strval(content);
    cache_get_row(i, 4, content);
    hausInfo[i][hX] = floatstr(content);
    cache_get_row(i, 5, content);
    hausInfo[i][hY] = floatstr(content);
    cache_get_row(i, 6, content);
    hausInfo[i][hZ] = floatstr(content);
    }

    printf("Haussystem: Es wurden %i Häuser geladen.", rows); // Ausgabe der Anzahl der geladenen Häuser.
    return 1;
    }


    Um den Vorgang des Ladens der Häuser zu vollenden fehlt noch der letzte Schritt, nähmlich der eigentlichen Generierung der Häuser auf der Karte. Wir erweitern unseren Callback also um eine Funktion, welche uns die Daten in Praktisches umwandeln soll und erweiteren unseren Enumerator um zwei Halter, welche jedoch nicht in der Datenbank abzuspeichern sind. Ebenfalls definieren wir am Kopf des Scriptes, wann ein String NULL ist:



    #define HAUS_TEXT_COLOR (0xE2A31DFF) // Farbe des 3D-Text-Labels


    #if !defined isnull
    #define isnull(%1) \
    ((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1]))))
    #endif


    enum hausEnumerator {
    hID, // Eindeutige ID des Hauses (Typ: INT)
    hPreis, // Preis zum Erwerb des Hauses (Typ: INT)
    hBesitzer[MAX_PLAYER_NAME], // Name des Besitzers des Hauses (Typ: VARCHAR ; Größe: MAX_PLAYER_NAME = 24)
    hInterior, // Interior des Hauses (nicht die InteriorID - mehr dazu später!) (Typ: INT)
    Float:hX, // X-Koordinate des Hauseingangs (Typ: FLOAT)
    Float:hY, // Y-Koordinate des Hauseingangs (Typ: FLOAT)
    Float:hZ, // Z-Koordinate des Hauseingangs (Typ: FLOAT)
    hCpID, // CheckpointID (Nicht in Datenbank abzuspeichern!)
    Text3D:h3DText // ID des 3DText-Labels (Nicht in Datenbank abzuspeichern!)
    };


    public OnGameModeLoadHouses() {
    new rows, fields, content[MAX_PLAYER_NAME]; // Deklaration der benötigten Variablen (content muss max. so groß wie ein Username sein).
    cache_get_data(rows, fields); // Die Anzahl der Reihen und Spalten der Ergebnismenge herauslesen und abspeichern.


    for(new i = 0; i != rows; i++) { // Schleife die Anzahl der Reihen (= Anzahl der Häuser) durchlaufen lassen.
    cache_get_row(i, 0, content); // Daten einer Zeile im String 'content' speichern.
    hausInfo[i][hID] = strval(content); // HausArray mit Enumerator mit dem Integer (strval()) füllen.
    cache_get_row(i, 1, content); // Wiederholung für andere Werte ...
    hausInfo[i][hPreis] = strval(content);
    cache_get_row(i, 2, content); // Feld steigt nach und nach wie auch im Query von links nach rechts.
    format(hausInfo[i][hBesitzer], MAX_PLAYER_NAME, "%s", content);
    cache_get_row(i, 3, content);
    hausInfo[i][hInterior] = strval(content);
    cache_get_row(i, 4, content);
    hausInfo[i][hX] = floatstr(content);
    cache_get_row(i, 5, content);
    hausInfo[i][hY] = floatstr(content);
    cache_get_row(i, 6, content);
    hausInfo[i][hZ] = floatstr(content);


    CreateHouseOnMap(i); // Übergabe des Indexes im Array zur einfacheren Handhabung.
    }

    printf("Haussystem: Es wurden %i Häuser geladen.", rows); // Ausgabe der Anzahl der geladenen Häuser.
    return 1;
    }


    stock CreateHouseOnMap(hausID) {
    // Erstellen eines neuen Checkpoints am Eingang des Hauses. Die CheckpointID wird im Array gespeichert.
    hausInfo[hausID][hCpID] = CreateDynamicCP(hausInfo[hausID][hX], hausInfo[hausID][hY], hausInfo[hausID][hZ], 1.5);


    new labelText[70]; // Erstellen eines neuen Strings zur Formatierung eines 3D-Text-Labels.
    if(isnull(hausInfo[hausID][hBesitzer])) { // Falls das Haus noch keinen Besitzer hat (dann ist der String NULL) ...
    format(labelText, sizeof(labelText), "- Dieses Haus ist zu kaufen! -\nPreis: $%i", hausInfo[hausID][hPreis]); // ... setze Text.
    } else { // Falls doch ...
    format(labelText, sizeof(labelText), "- Dieses Haus ist in Besitz! -\nBesitzer: %s", hausInfo[hausID][hBesitzer]); // ... setze Text.
    }


    // Erstellen des 3D-Text-Labels mithilfe des Streamers. Ebenfalls wird der Text in die Mitte des Checkpoints gesetzt und die Streamdistance auf 5 gesetzt,
    // sodass der Text nur in unmittelbarer Nähe zu sehen ist.
    hausInfo[hausID][h3DText] = CreateDynamic3DTextLabel(labelText, HAUS_TEXT_COLOR, hausInfo[hausID][hX], hausInfo[hausID][hY], hausInfo[hausID][hZ] - 0.3, 5);
    return 1;
    }


    >> Weiter zum 2. Teil (weiter unten!) ...

  • Nicht schlecht, doch das Geschriebene in Grün Irritiert sehr ... blicke da nicht durch.
    Du hast es gut BEschrieben und alles doch ein bissel zuviel

  • Wichtigste Grundbefehle


    Um das Haussystem nun belebbar zu machen werde ich im folgenden die wichtigsten Befehle für Admin und Spieler hinzufügen. Dabei sei zu sagen, dass diese sehr ausbaufähig sind und dieses Script weiterhin nur als Grundbaustein für ein ausgereiftes System dient. Beginnen wir damit, dass Spieler die Häuser betreten und verlassen können. Dabei kommen wir nun auf die InteriorID zu sprechen, welche eigentlich keine InteriorID ist, sondern einen Index im Array der vordefinierten Interiors darstellt. Sehet selbst:



    // Neuer Enumerator, welcher Informationen über vorhandene Interiors halten soll.
    enum hausInteriorDataEnumerator {
    Float:iX,
    Float:iY,
    Float:iZ,
    iInterior
    };

    new hausInteriorData[][hausInteriorDataEnumerator] = { // Neuer Array in Verbindung mit Enumerator mit direktem definieren der Interiors.
    {2495.8079, -1694.1421, 1014.7422, 3}, // iX, iY, iZ, iInterior
    {267.3732, 304.9254, 999.1484, 2},
    {1.5435, -3.2401, 999.4284, 2},
    {1262.4308, -785.4622, 1091.9063, 5}
    };


    COMMAND:enter(playerid, params[]) { // /enter zum Betreten der Häuser.
    for(new i = 0; i != MAX_HOUSES; i++) { // Durchlaufen aller Häuser, um zu testen, ob der Spieler einer aller Häuser betreten kann.
    if(IsPlayerInDynamicCP(playerid, hausInfo[i][hCpID])) { // Wenn der Spieler im Checkpoint eines Hauses steht, dann ...
    // ... setze die SpielerPosition in den Interior des Hauses (InteriorArray[Index][Koordinate]).
    SetPlayerPos(playerid, hausInteriorData[hausInfo[i][hInterior]][iX], hausInteriorData[hausInfo[i][hInterior]][iY], hausInteriorData[hausInfo[i][hInterior]][iZ]);
    SetPlayerVirtualWorld(playerid, hausInfo[i][hID]); // ... setze die virtuelle Welt eindeutig, damit kein Haus ein anderes stören kann.
    SetPlayerInterior(playerid, hausInteriorData[hausInfo[i][hInterior]][iInterior]); // ... setze den Interior mithilfe der vordefinierten Werte.
    break;
    }
    }
    return 1;
    }


    COMMAND:exit(playerid, params[]) {
    new playerWorld = GetPlayerVirtualWorld(playerid); // Auslesen der oben gesetzen virtuellen Welt.
    if(playerWorld != 0) { // Wenn der Spieler nicht im Freien ist (AUTO_INCREMENT wird niemals 0 als Index nehmen!), dann ...
    for(new i = 0; i != MAX_HOUSES; i++) { // ... durchlaufe alle möglichen Häuserdaten.
    if(hausInfo[i][hID] == playerWorld) { // Wenn die eindeutige Welt der Welt der Welt des Hauses entspricht, dann ...
    // ... teste, ob der Spieler in der Nähe der Türe im Interior ist.
    if(IsPlayerInRangeOfPoint(playerid, 3, hausInteriorData[hausInfo[i][hInterior]][iX], hausInteriorData[hausInfo[i][hInterior]][iY], hausInteriorData[hausInfo[i][hInterior]][iZ])) {
    SetPlayerPos(playerid, hausInfo[i][hX], hausInfo[i][hY], hausInfo[i][hZ]); // Falls ja, dann setze den Spieler zum Eingang des Hauses, ...
    SetPlayerVirtualWorld(playerid, 0); // ... setze die virtuelle Welt auf 0 (= Außen) und ...
    SetPlayerInterior(playerid, 0); // ... setze den Interior ebenfalls auf 0.
    }
    break;
    }
    }
    }
    return 1;
    }


    Da das System dynamisch sein soll, wollen wir also nun im folgenden per Befehl im Spiel Häuser erstellen können. Dazu ist wieder wie zuvor ein Query benötigt, jedoch diesmal ein Callback nur, um per mysql_insert_id() den Index, der per AUTO_INCREMENT gesetzt wurde, auszulesen und abzuspeichern. Wir erstellen also einen neuen Befehl und einen Callback, der zuerst geforwarded werden muss:



    forward OnPlayerCreateHouse(hausID); // Forwarded des Publics wie zuvor.


    COMMAND:createhouse(playerid, params[]) {
    if(IsPlayerAdmin(playerid)) { // Nur Admins sollten in der Lage sein, Häuser zu erstellen.
    new interior, preis; // Vordefinieren der benötigten Variablen.
    // Aufsplitten der eigegebenen Parameter in einzelne Variablen mithilfe des Custom-Delimiter. Falls falsche Werte, wird eine Nachricht ausgegeben.
    if(sscanf(params, "ii", interior, preis)) return SendClientMessage(playerid, COLOR_GREY, "Befehl: /createhouse [Interior] [Preis]");
    // Der Interior muss zuvor im hausInteriorData-Array definiert sein.
    if(interior >= sizeof(hausInteriorData)) return SendClientMessage(playerid, COLOR_GREY, "Dieser Interior steht nicht zur Verfügung.");
    // Es wäre unrealistisch, den Preis unter 0 zu setzen.
    if(preis < 0) return SendClientMessage(playerid, COLOR_GREY, "Sie müssen einen Preis für das Haus wählen, welcher über null liegt.");


    for(new i = 0; i != MAX_HOUSES; i++) { // Durchlaufen aller Häuserdaten, um einen freien Slot zu finden.
    if(hausInfo[i][hID] == 0) { // Sobald einer der Slots frei ist, dann ...
    new Float:X, Float:Y, Float:Z, query[160];
    GetPlayerPos(playerid, X, Y, Z); // ... jetzigen Standpunkt des Admins herauslesen.
    hausInfo[i][hPreis] = preis; // Setzen der neuen Hausdaten (Preis, Interior, Koordinaten).
    hausInfo[i][hInterior] = interior;
    hausInfo[i][hX] = X;
    hausInfo[i][hY] = Y;
    hausInfo[i][hZ] = Z;

    CreateHouseOnMap(i); // Praktische Realisierung des Hauses auf der Karte.
    // Abspeichern der Daten in der Datenbank - Formatierung per mysql_format().
    mysql_format(sqlHandle, query, "INSERT INTO `breadfish_houses` (`hPreis`, `hInterior`, `hX`, `hY`, `hZ`) VALUES ('%i', '%i', '%f', '%f', '%f')", hausInfo[i][hPreis],
    hausInfo[i][hInterior],
    hausInfo[i][hX],
    hausInfo[i][hY],
    hausInfo[i][hZ]);
    // Absenden des Querys mit Aufruf des Callbacks 'OnPlayerCreateHouse' nach Vollendigung des Querys.
    mysql_function_query(sqlHandle, query, false, "OnPlayerCreateHouse", "i", i);
    return 1; // Stoppen des Befehls, sobald das Haus erstellt wurde.
    }
    }
    // Es konnte kein Haus erstellt werden, da alle Slots belegt sind - Fehlermeldung in Console!
    print("Haussystem: Alle Häuserslots sind belegt, heben Sie MAX_HOUSES an, um neue Häuser erstellen zu können.");
    } else {
    SendClientMessage(playerid, COLOR_GREY, "Sie sind nicht dazu befugt, diesen Befehl zu verwenden.");
    }
    return 1;
    }


    public OnPlayerCreateHouse(hausID) {
    hausInfo[hausID][hID] = mysql_insert_id(); // Auslesen des Indexes aus der Datenbank, der per AUTO_INCREMENT gesetzt wurde.
    return 1;
    }


    Nachdem nun ein Haus betreten und verlassen werden kann und Häuser von Admins im Spiel erstellt werden können fehlt eigentlich für's erste nur noch die Möglichkeit, Häuser zu kaufen bzw. zu verkaufen. Gehen wir dazu wie folgt vor:



    #define COLOR_GREY (0xB5B5B5FF) // Definieren zweier neuer Farben, ...
    #define COLOR_GREEN (0x80A05CFF) // zur Verschönerung von Ausgaben.


    COMMAND:buyhouse(playerid, params[]) { // /buyhouse, um ein Haus zu kaufen.
    for(new i = 0; i != MAX_HOUSES; i++) { // Durchlaufen aller Hausdaten, um ...
    if(IsPlayerInDynamicCP(playerid, hausInfo[i][hCpID])) { // ... zu testen, ob der Spieler sich in einem Checkpoint eines Hauses befindet.
    // Ein zu kaufendes Haus darf über keinen Besitzer verfügen.
    if(!isnull(hausInfo[i][hBesitzer])) return SendClientMessage(playerid, COLOR_GREY, "Dieses Haus ist bereits im Besitz einer anderen Person.");
    // Falls der Spieler nicht das entsprechende Geld bei sich hat, soll der Befehl unterbrochen werden.
    if(GetPlayerMoney(playerid) < hausInfo[i][hPreis]) return SendClientMessage(playerid, COLOR_GREY, "Sie haben nicht genügend Geld bei sich, um dieses Haus zu finanzieren.");
    new labelText[70], query[90], clientName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, clientName, sizeof(clientName)); // Auslesen des Spielernamens zum Setzen des neuen Besitzernamens.
    GivePlayerMoney(playerid, -hausInfo[i][hPreis]); // Preis zur Finanzierung des Hauses dem Spieler abziehen.
    format(hausInfo[i][hBesitzer], MAX_PLAYER_NAME, "%s", clientName); // Neuen Besitzer setzen.
    format(labelText, sizeof(labelText), "- Dieses Haus ist in Besitz! -\nBesitzer: %s", hausInfo[i][hBesitzer]); // 3D-Text-Label Text erneuern und ...
    UpdateDynamic3DTextLabelText(hausInfo[i][h3DText], HAUS_TEXT_COLOR, labelText); // ... neu setzen.
    // Übernehmen des neuen Besitzers in der MySQL Tabelle (Hier ist weder Callback noch Parameter noch Format benötigt, daher 3x "").
    mysql_format(sqlHandle, query, "UPDATE `breadfish_houses` SET `hBesitzer` = '%e' WHERE `hID` = '%i'", hausInfo[i][hBesitzer], hausInfo[i][hID]);
    mysql_function_query(sqlHandle, query, false, "", "", "");
    // Ausgabe an den Spieler, dass er nun im Besitz eines neuen Hauses ist.
    SendClientMessage(playerid, COLOR_GREEN, "Herzlichen Glückwunsch {FFFFFF}zu Ihrem neuen Haus.");
    }
    }
    return 1;
    }


    COMMAND:sellhouse(playerid, params[]) { // /sellhaus, um ein Haus zu verkaufen.
    for(new i = 0; i != MAX_HOUSES; i++) { // Durchlaufen aller Hausdaten, um ...
    if(IsPlayerInDynamicCP(playerid, hausInfo[i][hCpID])) { // ... zu testen, ob der Spieler sich in einem Checkpoint eines Hauses befindet.
    new clientName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, clientName, sizeof(clientName)); // Auslesen des Spielernamens zum Vergleich mit den Hausdaten.
    // Falls der Spieler der Besitzer ist (isnull(), da strcmp() 0 zurückgibt, wenn eine der Strings NULL ist), dann ...
    if(!isnull(hausInfo[i][hBesitzer]) && strcmp(hausInfo[i][hBesitzer], clientName) == 0) {
    new labelText[70], query[70];
    GivePlayerMoney(playerid, hausInfo[i][hPreis]); // ... Geld zurücküberweisen.
    format(hausInfo[i][hBesitzer], MAX_PLAYER_NAME, "%s", EOS); // ... Hausbesitzer in Hausdaten auf NULL setzen.
    format(labelText, sizeof(labelText), "- Dieses Haus ist zu kaufen! -\nPreis: $%i", hausInfo[i][hPreis]); // ... 3D-Text-Label Text erneuern und ...
    UpdateDynamic3DTextLabelText(hausInfo[i][h3DText], HAUS_TEXT_COLOR, labelText); // ... neu setzen.
    // Übernehmen der neuen Hausdaten in der MySQL Tabelle (Hier ist weder Callback noch Parameter noch Format benötigt, daher 3x "").
    mysql_format(sqlHandle, query, "UPDATE `breadfish_houses` SET `hBesitzer` = NULL WHERE `hID` = '%i'", hausInfo[i][hID]);
    mysql_function_query(sqlHandle, query, false, "", "", "");
    // Ausgabe an den Spieler, dass ihm das Geld zurück erstattet wurde.
    SendClientMessage(playerid, COLOR_GREEN, "Sie haben Ihr Haus erfolgreich verkauft und den Wert des Hauses überwiesen bekommen.");
    }
    break;
    }
    }
    return 1;
    }


    Abschließendes


    Sooo ... das meisten hätten wir. Häuser werden nun also wunderbar beim Start des Gamemodes geladen und Spieler sind in der Lage, Häuser zu erstellen, diese zu betreten bzw. zu verlassen und die Häuser zu kaufen bzw. zu verkaufen. Fügen wir noch den letzen Feinschliff hinzu, indem wir noch Ausgaben beim Betreten eines Häusercheckpoints hinzufügen. Dazu verwenden wir einen von Incognito's bereitgestellten Callback (OnPlayerEnterDynamicCP()), welcher einmal beim Betreten eines dynamischen Checkpoints aufgerufen wird. Ebenfalls fügen wir noch die Beendigung der MySQL-Verbindung hinzu, sobald der GameMode gestoppt wird.



    public OnGameModeExit() { // Sobald der GameMode geschlossen wird, ...
    mysql_close(); // ... wird die Verbindung zur Datenbank gekappt.
    return 1;
    }


    public OnPlayerEnterDynamicCP(playerid, checkpointid) { // Sobald ein Spieler einen gestreamten Checkpoint betritt, ...
    for(new i = 0; i != MAX_HOUSES; i++) { // ... durchlaufen wir alle Hausdaten, um zu testen, ...
    if(checkpointid == hausInfo[i][hCpID]) { ... ob die CheckpointID der CheckpointID des Hauses entspricht.
    if(isnull(hausInfo[i][hBesitzer])) { // Falls dieses Haus nicht im Besitz eines Spielers ist, ...
    // ... geben wir folgende Nachricht aus:
    SendClientMessage(playerid, COLOR_GREEN, "Dieses Haus steht zum Verkauf offen! Verwende {FFFFFF}/buyhouse{80a05c}, um das Haus zu erwerben.");
    } else { // Ansonsten, ...
    new message[140];
    // ... formatieren wir einen anderen String und geben ihn anschließend dem Spieler aus.
    format(message, sizeof(message), "Willkommen an {FFFFFF}%s's{80a05c} Vordach, verwenden Sie {FFFFFF}/enter{80a05c}, um das Haus zu betreten.", hausInfo[i][hBesitzer]);
    SendClientMessage(playerid, COLOR_GREEN, message);
    }
    break;
    }
    }
    return 1;
    }


    Fertiger Quellcode (ohne Kommentare): Click here!


    Als Résumé kann man also stehen lassen, dass ein Haussystem eigentlich recht simpel ist und eigentlich auf sehr wenig Quellcode basiert. Nur das Ausbauen des Scripts durch Befehle und anderen Schnick-Schnack lässt das ganz Haussystem sehr komplex erscheinen, wobei eigentlich immer nur wenige Daten bearbeitet werden müssen. Wichtig ist eben, dass man an ein solches System immer geplant und strukturiert herangeht, da falsche Strukturen einem meist das Einfache im System rauben. Also nehmt euch Zeit, plant, was ihr dazu verwenden könnt und wollt und wie ihr mit den einzelnen Bausteinen umgeht. Ich wiederhole zum Ende des Tutorials nochmals, dass es sich hierbei lediglich um ein Grundgerüst handelt - Untersysteme wie Mietsysteme etc. sind hier nun sehr einfach einzubauen und haben kaum Einfluss auf den Rest des Quellcodes.

  • Nicht schlecht, doch das Geschriebene in Grün Irritiert sehr ... blicke da nicht durch.
    Du hast es gut BEschrieben und alles doch ein bissel zuviel


    Mit dem 2. Teil des Tutorials ist nun auch ein fertiger Quellcode hinzugekommen ohne Kommentare. Demnach könnte dies evt. etwas für dich sein, um sich das System als Ganzes anzuschauen.

  • Ist definitiv nicht für Anfänger geeignet.
    Dennoch gute Erklärungen und ein anschauliches Format!
    Gute Arbeit :)


    Das ist korrekt. Ein wenig Vorkenntnis über Pawno und MySQL sollte bereits vorhanden sein, dennoch ist es trotzdem für jeden geeignet, der sich mal Gedanken über ein Haussystem machen möchte (auch Anfänger, damit diese sich ein Bild machen können). Denn im Tutorial wird das Handhaben von Queries und der einzelnen Funktionen erklärt. Weiterhin werden alle Funktionen in den Kommentaren angesprochen und zu mindest am Rande erklärt. Für Anfänger wird es einfach etwas länger dauern, da diese sich die Funktionen evt. genauer im Wiki anschauen möchten. Aber dieses Tutorial ist ja auch nicht dazu gedacht, die Basics des Scriptens zu erläutern. Wenn man bisher keine Ahnung vom Scripten hat, sollte man sich auch nicht mit Haussystemen befassen, sondern mit kleineren Bausteinen, die evt. Teil eines Haussystems sind.


  • Das ist korrekt. Ein wenig Vorkenntnis über Pawno und MySQL sollte bereits vorhanden sein, dennoch ist es trotzdem für jeden geeignet, der sich mal Gedanken über ein Haussystem machen möchte (auch Anfänger, damit diese sich ein Bild machen können). Denn im Tutorial wird das Handhaben von Queries und der einzelnen Funktionen erklärt. Weiterhin werden alle Funktionen in den Kommentaren angesprochen und zu mindest am Rande erklärt. Für Anfänger wird es einfach etwas länger dauern, da diese sich die Funktionen evt. genauer im Wiki anschauen möchten. Aber dieses Tutorial ist ja auch nicht dazu gedacht, die Basics des Scriptens zu erläutern. Wenn man bisher keine Ahnung vom Scripten hat, sollte man sich auch nicht mit Haussystemen befassen, sondern mit kleineren Bausteinen, die evt. Teil eines Haussystems sind.


    Ich muss auch sagen das dein Tutorial gut gelungen ist wie deine anderen davor auch
    vor allem finde ich es auch sehr gut das du mal das R7 Plugin mit ein gebracht hast da die verwendung davon hier im
    Forum ziemlich gering ist. Vielleicht werden sich auch paar mehr danach umsehen da dieses Plugin im Bereich MySQL fast nicht zu toppen ist.

  • [10:27:10] Loading plugin: mysql.so
    [10:27:10] Failed (plugins/mysql.so: undefined symbol: _Z13stringvprintfPKcPc)
    [10:27:10] Loading plugin: sscanf.so


    Ich benutze das MySQL System V7 für Linux und bekomme leider nur einen Fehler. Bitte einmal genau erklären wie du den oben angegebenen Fehler umgehst. Ich möchte hier keine andere MySQL Version, lediglich eine Lösung.

    Einmal editiert, zuletzt von Tigerchen ()

  • [10:27:10] Loading plugin: mysql.so
    [10:27:10] Failed (plugins/mysql.so: undefined symbol: _Z13stringvprintfPKcPc)
    [10:27:10] Loading plugin: sscanf.so


    Ich benutze das MySQL System V7 für Linux und bekomme leider nur einen Fehler. Bitte einmal genau erklären wie du den oben angegebenen Fehler umgehst. Ich möchte hier keine andere MySQL Version, lediglich eine Lösung.


    MySQL R7 hatte unter Linux schon des öfteren Probleme bereitet. Folgende Lösung für das Problem wurde im englischen Forum gefunden: http://forum.sa-mp.com/showpost.php?p=1741866&postcount=2248