Diese Dokumentation steht, wie IDK selbst, in stetiger und aktiver Entwicklung. Da manche Teile des Emulators nicht so vollständig dokumentiert sind, wie sie zu sein haben, empfehlen sich Tests und ein gesunder Menschenverstand. Nichtsdestotrotz werden Verbesserungen, welche Informationen über bestimmte Methoden und Klassen erweitern, begrüßt.

Während dieser Einführung werden Beispiele in Grafiken sowie in Textform dargestellt, weshalb sich ein moderner Browser zur Anschau empfiehlt. Die Mehrzahl unseres Teams nutzt den Google Chrome Browser und Mozilla Firefox.

Die Möglichkeiten, die dir für Plugins bereitstehen, sind nahezu grenzenlos. Doch sind es spezielle Bereiche, welche in dieser Einleitung in den Fokus rücken werden.

  • Bot-Interaktoren

    Mit eigenen Bot-Interaktoren lassen sich Funktionen schreiben, welche für eine Menge an Bots verfügbar sein sollen. Diese Bots können auf bestimmte Ereignisse reagieren, wie z.B. auf einen Spieler, welcher eine Chat-Nachricht in einen Raum schickt.

  • Chat-Kommandos

    Auch lassen sich neue Kommandos einfügen, denen ein Name, eine Berechtigung und optionale Schreibweisen hinzugefügt werden können.

  • Spezielle Events

    Bestimmte Aktionen, welche nicht durch einen Bot festgestellt werden können, sind anderweitig registrierbar. Darunter fällt z. B. der Kauf eines Items.

  • Asynchrone Funktionen

    Hiermit wird das Ausführen von nebenläufigen Aufgaben ermöglicht, welche z. B. dann benötigt werden, wenn Informationen aus dem Internet heruntergeladen werden müssen.

Wir denken, dass die Implementierung von neuen Funktionen in Retro Hotels nicht von dem gesamten Neustart des Servers abhängig sein darf. Denn diese verhindern den normalen Ablauf und nehmen Spielern den Spaß. Man nehme an, es wird eine Funktion gebraucht, welche um 17:00 angefragt wird und bis um 18:30 fertig zu sein hat. So müsste das Hotel in der wichtigsten Betriebszeit ausfallen und Nutzer wären verärgert, würden sogar im schlimmsten Fall ausbleiben. Dies ist einer der Hauptgründe, welcher uns dazu veranlasst hat, ein System für die Plugin-Entwicklung zu realisieren.

Außerdem besitzen Script-Sprachen wie JavaScript einen simplen Sprachaufbau, sodass auch Anfänger mit etwas Fachwissen damit zurechtkommen können. Durch dieses neue Gebiet werden auch im technischen Bereich eines Hotels neue "Berufsbereiche" erschaffen, in welchen Entwickler mit nötiger Erfahrung im Entwickeln von Plugins für IDK einen gewissen Vorteil genießen.

Durch die optionale Vermarktung von Plugins im Placeholder-Network-Store, auf den alle IDK-Kunden Zugriff haben, lässt sich bei Brauchbarkeit des Plugins auch Geld verdienen. Davon profitieren meist beide Parteien, da dem Hotel-Besitzer das einfache Hinzufügen von neuen Funktionen mit Updates ermöglicht wird und der Entwickler selbst dafür eine Vergütung bekommt.

Es wird nicht viel für das Entwickeln eines IDK-Plugins benötigt. So reicht ein funktionsfähiger IDK-Server, konfiguriert mit einer passenden Datenbank, ein Client und optional einen IDE für JavaScript oder für die Script-Sprache, für die sich entschieden wurde. Um nun aber auch an bestimmte Methoden- und Klassennamen zu kommen, steht eine generierte Javadocs-Seite hier auf unserer Homepage bereit.

Das Plugin wird in eine neue Datei geschrieben und mit der passender Dateiendung (z. B. .js bei JavaScript) in den Ordner "plugins" abgespeichert. Falls Java-Libraries benötigt werden, müssen diese als .jar Dateien in den Ordner "libs" geschoben werden. Danach lassen sie sich im Plugin verwenden.

Fehler sind menschlich und können passieren. Aus diesem Grund gibt es auch Fehlermeldungen, welche (hoffentlich) bei Problemen ausgegeben werden.

Falls es dazu kommen sollte, dass etwas schon an der Syntax des Scripts nicht korrekt ist, so wird die Fehlermeldung bereits beim Laden des Plugins angezeigt. Doch für den Fall, dass beispielsweise bei einem Kommando-Aufruf eine nicht existente Funktion ausgeführt werden soll, wird die Meldung erst nach dem Ausführen des Kommandos erzeugt.

Da bei Plugins für IDK nicht nur die Implementierung der Script-Sprache selbst, sondern auch die von Java in Betracht gezogen wird, sind Komplikationen möglich. Mehr Informationen über den für Java geschriebenen JavaScript-Interpreter Nashorn sind hier verfügbar.

Wenn auch nach intensiver Suche im Quelltext und mittels Suchmaschinen ein Problem nicht gelöst werden kann, ist ein auf Twitter verfügbarer Support möglich. Hierzu bestenfalls @WinfieldSteve und / oder @Rhinodanny kontaktieren.

Um die Entwicklung für Programmierer leichter zu gestalten, haben wir dem Emulator Funktionen hinzugefügt, welche das Neuladen von lokalen Plugins vereinfachen und den Neustart des Emulators überflüssig machen.

Hierfür gibt es drei Möglichkeiten, welche unterstützt werden

  • Den Neustart des Servers
  • Das Ausführen von "refreshlocalplugins" in der Server-Konsole
  • Das Ausführen des Kommandos ":refresh_plugins" im Hotel

Dabei sei angemerkt, dass Fehler, welche nicht schon beim Ausführen der "initializePlugin" Funktion des Plugins auftreten, nur auf der Server-Konsole zu sehen sind. Außerdem wird das Event ".onLoaded" von Bot-Interaktoren nur beim Laden des Raums ausgeführt, was mit dem Kommando ":refresh_room" und dem erneuten Betreten erzwingt werden kann.

Um Fehler, Warnungen und Informationen an die Server-Konsole auszugeben, eignet sich das Log4j Objekt, welches für jedes Plugin erstellt wird. Über die Methode "IDK.getLogger()" lässt sich darauf zugreifen und damit arbeiten.

Einige Funktionen des Loggers

  • IDK.getLogger().info(String message)
  • IDK.getLogger().warn(String message)
  • IDK.getLogger().error(String message)
  • IDK.getLogger().debug(String message)

Wie in der Software-Branche üblich ist es auch hier das erste Privileg eines angehenden Plugin-Programmierers, ein Programm zu schreiben, welches stumpf den Text "Hallo Welt" auf die Konsole ausgibt.

var plugin = {
    'name': 'Hallo-Welt-Plugin',
    'description': 'Gibt den Text "Hallo Welt" auf die Konsole aus.'
};

function initializePlugin() {
    print("Hallo Welt");
}

function unloadPlugin(isShutdown) {
    return true;
}

Nachdem der gesehene Code noch einmal verinnerlicht wurde, kann es nun an die Erklärung gehen.

Das erste hier Definierte ist ein Objekt, welches das Plugin näher beschreibt. Dies kann für den Entwickler selbst nützlich sein, wie auch für jeden anderen, um den Zweck der Applikation naheliegend zu lassen.

Die Start-Funktion "initializePlugin" wird von IDK dann ausgeführt, wenn der Emulator das Plugin erfolgreich geladen hat. In ihr können unter anderem Interaktoren, Kommandos und Events registriert werden.

Die Funktion "unloadPlugin" wird ausgeführt, wenn das Plugin neugeladen werden muss oder der Emulator dabei ist, heruntergefahren zu werden. Ob es sich um letzteres handelt, verrät der Boolean "isShutdown", welcher der Funktion mitgegeben wird. Der Rückgabewert entscheidet, ob sich das Plugin in diesem Moment neuladen darf, was bei einem "false" unterlassen wird, falls der Server nicht gerade gestoppt wird. Dies ist z. B. wichtig, wenn das Plugin aktuell Event-Runden am Laufen hält.

Dies zu der Struktur eines korrektgeschriebenen IDK-Plugins.

Alle Klassen und Packages des IDK-Servers werden automatisch in das Plugin importiert, sodass ohne Umstände mit diesen gearbeitet werden kann. Jedoch gibt es gewisse Bereiche, welche nicht direkt für den Entwickler verfügbar sind. So z.B. die Klasse "File" des "java.io" Package. Hierzu gibt es für JavaScript-Plugins zwei einfache Methoden:

importClass(java.io.File); // import java.io.File;
importPackage(java.io); // import java.io.*;

Dies ist jedoch nicht für Klassen nötig, welche sich im Package "org.stevewinfield.suja.idk" befinden.

Oft sammelt ein Plugin Daten, welche es später wieder benutzen möchte. Hierfür bieten wir eine Storage Klasse, die das Speichern in der IDK MySQL-Datenbank ermöglicht.

Die zwei wichtigsten Klassen, welche zum Abfragen und Ausführen von SQL-Queries benutzt werden sollten, sind "PreparedStatement" und "ResultSet". Wie die Namen schon verraten, handelt es sich um eine Klasse, welche die Query "vorbereitet", und um eine Klasse, welche im Endeffekt das Ergebnis liefert. Das Vorbereiten beinhaltet unter anderem das Filtern von Strings, sodass Sicherheitslücken vermieden werden können.

In diesem Fall soll die ID des Spielers mit dem Namen "CoolerEntwickler" gesucht und ausgegeben werden:

var statement = Bootloader.getStorage().queryParams("SELECT id FROM players WHERE nickname = ? ");
statement.setString(1, "CoolerEntwickler");

var result = statement.executeQuery();

if (result.next()) {
    print("ID: " + result.getInt("id"));
} else {
    print("Der Nutzer existiert nicht.");
}

Randbemerkung: Die Klasse Bootloader ist eine Art "Anfangs"-Klasse, welche statische Variablen und Methoden enthält, die den Zugriff auf gewisse Bereiche ermöglichen. Sie beinhaltet die Methode, die beim Starten des Servers ausgeführt wird.

Die Methode ".queryParams(String query)" erzeugt ein "PreparedStatement" mit welchem dann mittels der Funktion ".setString(int index, String value)" ein mit dem Zeichen "?" gesetzter Platzhalter ersetzt werden kann. Der Index hierfür beginnt bei 1 und erhöht sich pro gesetzten Platzhalter. Wir empfehlen / verlangen ein deartiges Filtern für Strings!

Hier ein Fall, in welchem die Daten mehrere Nutzer benötigt werden (Die ID von allen Nutzern, die mehr als 100 Taler besitzen):

var statement = Bootloader.getStorage().queryParams("SELECT id FROM players WHERE credits_balance > 100 ");
var result = statement.executeQuery();

while (result.next()) {
    print("ID: " + result.getInt("id"));
}

Für Queries, welche kein Ergebnis liefern, so z. B. UPDATE, INSERT und DELETE, muss statt der Methode ".executeQuery()" die Methode ".execute()" ausgeführt werden.

Wie in Standard-Java ist es hier ebenfalls möglich Daten aus dem Internet zu laden. Um dies einfacher zu gestalten, ist die Library "Apache Commons IO (2.4)", mit welcher es möglich ist, Streams direkt in Strings zu lesen, in IDK enthalten.

In diesem Beispiel wird die Temperatur von Berlin über die API von "openweathermap.org" in JSON geladen:

importClass(java.net.URL); // import java.net.URL;
importClass(org.apache.commons.io.IOUtils); // import org.apache.commons.io.IOUtils;

var websiteResult = IOUtils.toString(new URL("http://api.openweathermap.org/data/2.5/weather?q=Berlin&lang=de&units=metric").openStream());
var jsonData = JSON.parse(websiteResult);

print("In Berlin sind es aktuell " + jsonData.main.temp + " Grad Celsius!");

Ähnlich lässt sich dieses Beispiel auch für lokale Dateien umschreiben.

Die Möglichkeit zum Erstellen von Bot-Interaktoren ist eine der wichtigsten Bestandteile des IDK-Plugin-Systems. Bot-Interaktoren, welche Bots in der Datenbank zugeteilt werden können, kontrollieren das Verhalten der Bots.

Das Erstellen eines Bot-Interaktors ist simpel. Man erstellt ein Objekt mit den Events, auf welche man reagieren möchte, und registriert dieses Objekt dann mit einer Interactor-Id, welche eine für einen Interaktor einzigartige Zahl ist. Diese ID wird nun den Bots in der Spalte "interactor" der Tabelle "room_bots" zugeteilt, welche den Interaktor nutzen sollen.

Folgende Events sind für Bot-Interaktoren möglich:

onLoaded(RoomInstance room, RoomPlayer bot) wird ausgeführt, wenn der Bot bzw. der Raum geladen wird. Dies ist meistens die Funktion, die einem Interaktor mitteilt, dass er jetzt mit dem Bot arbeiten kann.

In diesem Fall soll der Bot anfangen zu tanzen, wenn der Raum geladen wurde:

onLoaded: function(room, bot) {
    bot.dance(1);
}

onPlayerSays(RoomPlayer player, RoomPlayer bot, ChatMessage message) wird ausgeführt, wenn ein Nutzer etwas im Raum des Bots sagt. Dabei ist jedoch anzumerken, dass "message" ein Objekt der Klasse "ChatMessage" ist und somit nicht die direkte Nachricht liefert. Diese kann durch die Methode ".getMessage()" angezeigt werden.

In diesem Fall soll der Bot "Hallo" antworten, wenn der Nutzer "hi" schreibt:

onPlayerSays: function(player, bot, message) {
    if (message.getMessage().toLowerCase() == "hi") {
        bot.chat("Hallo");
    }
}

Randbemerkung: ".toLowerCase()" wird hier verwendet, um die Groß- und Kleinschreibung zu ignorieren.

onCycle(RoomPlayer bot) wird ausgeführt, wenn dies über die Methode ".requestBotCycles(int cycles)" angefragt wurde. Mit dieser Methode kann ein verspätetes Ausführen von bestimmten Funktionen ermöglicht werden. Der Parameter "cycles" ist die Anzahl der Zyklen, welche überspringt werden sollen, bevor "onCycle" ausgeführt wird. Ein Zyklus dauert ca. 0.5 Sekunden, sodass 2 Zyklen meistens etwa 1 Sekunde ergeben.

In diesem Fall soll der Bot von Anfang an, bis der Raum geschlossen wird, winken:

onLoaded: function(room, bot) {
    bot.requestBotCycles(1); // 0.5 Sekunden Verzögerung am Anfang
},
onCycle: function(bot) {
    bot.wave();
    bot.requestBotCycles(4); // 2 Sekunden Verzögerung (erneuter Zyklus)
}

onLeft(RoomInstance room, RoomPlayer bot) wird ausgeführt, wenn der Raum geschlossen wurde oder der Bot von einem Nutzer gekickt wird.

In diesem Fall soll der Bot sich verabschieden, wenn er den Raum "verlässt":

onLeft: function(room, bot) {
    bot.chat("Aufwiedersehen!");
}

Hinzugefügt wird der Interaktor nun mit der Funktion "IDK.addBotInteractor(int interactorId, Object interactor)" in der ".initializePlugin" Funktion des Plugins.

In diesem Fall wird ein Bot-Interaktor mit der ID 11 erstellt, welcher den Bot anfangen lassen soll zu tanzen, wenn wir im Raum "tanz bot" sagen:

var plugin = {
    'name': 'Tanz-Bot-Plugin',
    'description': 'Lässt einen Bot mit dem Interaktor 11 auf Kommando tanzen.'
};

var TanzBot = {
    onPlayerSays: function(player, bot, message) {
        if (message.getMessage().toLowerCase() == "tanz bot") {
            bot.dance(1);
        }
    }
};

function initializePlugin() {
    IDK.addBotInteractor(11, TanzBot);
}

function unloadPlugin(isShutdown) {
    return true;
}

Einfügen lässt sich ein Bot für diesen Interaktor (mit der ID 11) dann einfach mit folgender SQL-Query:

INSERT INTO room_bots (start_room_id, nickname, figurecode, gender, motto, start_rotation, start_position_x, start_position_y, start_position_altitude, moving_enabled, interactor)
VALUES (1, "TanzBot", "hr-831-45.fa-1206-91.sh-290-1331.ha-3129-100.hd-180-2.cc-3039-73.ch-3215-92.lg-270-73", "m", "Ich tanze gerne!", 2, 10, 11, 0, 1, 11);

Da jede Bot-Interaktor-Id einzigartig sein muss und sich die IDs nicht überschneiden dürfen, empfehlen wir, die ID zu dem vom Plugin generierten Hash Code, auf welchen über die Methode "IDK.getHash()" zugegriffen werden kann, zu addieren. Um an den Hash-Code zu kommen, empfiehlt sich das einmalige Ausgeben über die "print"-Methode und das Notieren von diesem.

function initializePlugin() {
    // print(IDK.getHash());
    // => 260850349
    
    IDK.addBotInteractor(IDK.getHash() + 1, TanzBot); // 260850350
    IDK.addBotInteractor(IDK.getHash() + 2, WinkBot); // 260850351
    IDK.addBotInteractor(IDK.getHash() + 3, SprechBot); // 260850352
}

Das Definieren von eigenen Chat-Kommandos ermöglicht eine Vielzahl von neuen und unbegrenzten Möglichkeiten. Dank der einfachen Handhabung unseres Rechte-Systems ist es auch kein Problem, diese auf eine kleine Menge an Nutzern zu beschränken.

Ähnlich verhalten sich Chat-Kommandos zu Bot-Interaktoren in ihrer Objekt-Struktur, welche jedoch in diesem Fall nur ein Event umgeben kann. Durchaus vielseitiger ist aber in diesem Bereich das Registrieren der Objekte.

execute(RoomPlayer player, ChatCommandArguments arguments) wird dann ausgeführt, wenn ein Nutzer das registrierte Kommando benutzt. Übergeben wird hierbei der Spieler "player" und ein Objekt "arguments", welches die Argumente, die vom Nutzer angegeben wurden, festhält. Nähere Informationen zur Nutzungsweise der Klasse "ChatCommandArguments" gibt es hier.

Der Rückgabewert der Funktion "execute" sollte, wenn alles funktioniert hat, immer "true" ergeben. Andernfalls wird eine Fehlermeldung ausgegeben, die besagt, dass etwas schief gelaufen ist.

In diesem Fall soll dem Spieler ein Kaffee gegeben werden, wenn er das Kommando ausführt:

execute: function(player, arguments) {
	player.handleVending(8);
	return true;
}

Das Besondere an den Chat-Kommandos ist die Weise, wie man sie registriert. Hierfür gibt es nämlich mehrere Möglichkeiten, welche wir nicht vorenthalten möchten.

  • IDK.addChatCommand(String name, Object commandObject) verlangt nur den Kommando-Namen und das Kommando-Objekt.

    In diesem Fall kann es von jedem Spieler benutzt werden.

    // Das Kommando, welches alle Kommandos auflistet, benötigt keine besondere Berechtigung,
    // weil es von jedem Nutzer verwendet werden darf.
    IDK.addChatCommand("commands", CommandListCommand);
    
  • IDK.addChatCommand(String name, String permission, Object commandObject) verlangt den Kommando-Namen, eine Berechtigung und das Kommando-Objekt.

    Die Berechtigung, welche letztendlich festgelegt werden muss, wird für jeden Nutzer oder Rang individuell in der Datenbank-Tabelle "level_rights" geprüft.

    // Das Kommando, welches jeden Nutzer im Raum zum Schweigen bringt, benötigt eine besondere Berechtigung,
    // weil es nur von Mitarbeitern benutzt werden darf.
    IDK.addChatCommand("roommute", "command_roommute", RoomMuteCommand);
    
  • IDK.addChatCommand(String name, String[] aliases, String permission, Object commandObject) verlangt den Kommando-Namen, optionale Schreibweisen, eine Berechtigung und das Kommando-Objekt.

    Die optionalen Schreibweisen werden in einen String-Array verpackt, also z. B. "['cmd_1', 'cmd1', 'cmd_eins']".

    // Das Kommando, welches den Katalog neu lädt, ist den Mitarbeitern verschieden bekannt.
    // Deshalb werden ihnen mehrere Schreibweisen zur Verfügung gestellt.
    IDK.addChatCommand("refresh_catalog", ["update_catalogue", "update_shop", "refresh_shop"], "command_refresh_catalog", RefreshCatalogCommand);
    
  • IDK.addChatCommand(String name, String usage, String permission, Object commandObject) verlangt den Kommando-Namen, eine Nutzungs-Anweisung, eine Berechtigung und das Kommando-Objekt.

    Die Nutzungs-Anweisung stellt die allgemeine Struktur des Kommandos dar. Dabei ist "<command>" ein Platzhalter, welcher automatisch mit der von dem Spieler benutzten Schreibweise ersetzt wird. Ein gültiger "usage"-String für ein Kick-Kommando wäre unter anderem ":<command> [Spielername] (Grund)", wobei eckige Klammern immer ein benötigtes Argument darstellen und runde Klammern ein Optionales.

    // Das Kommando, welches einen Link an die Nutzer im Hotel teilt, bietet eine Nutzungs-Anweisung,
    // da diese oft von den Mitarbeitern vergessen wird.
    IDK.addChatCommand("link_alert", ":<command> [Link-URL] (Nachricht)", "command_link_alert", LinkAlertCommand);
    
  • IDK.addChatCommand(String name, String usage, String[] aliases, String permission, Object commandObject) verlangt den Kommando-Namen, eine Nutzungs-Anweisung, optionale Schreibweisen, eine Berechtigung und das Kommando-Objekt.

    Hier werden optionale Schreibweisen und die Nutzungs-Anweisung kombiniert registriert.

    // Das Kommando, welches einen Nutzer zum Schweigen bringt, bietet eine Nutzungs-Anweisung
    // sowie zwei optionale Schreibweisen, um den Moderatoren die Arbeit zu erleichtern.
    IDK.addChatCommand("mute", ":mute [Spielername] (Länge in Minuten)", ["shutup", "be_quiet"], "command_mute", MuteCommand);
    

Im Falle, dass eine Berechtigung in der Funktion benötigt wird, man jedoch diese nicht setzen möchte, ist es möglich sie mit einem leeren String zu ignorieren. Wie bei den Bot-Interaktoren werden die Chat-Kommandos auch in der Funktion ".initializePlugin" definiert.

Auf Ereignisse, wie beispielsweise der Kauf eines Items im Katalog oder der Login eines Spielers, kann mittels sogenannten Event-Listenern reagiert werden. Diese sind Objekte, welche auf je ein bestimmtes Ereignis bzw. Event horchen. Eine Liste dieser Events findest du hier.

onEvent(Event event) wird ausgeführt, wenn das Event, auf das man horcht, ausgelöst wird. Dabei ist der Parameter "event" von dem Typen des Events auf das gehorcht wird.

In diesem Fall möchten wir auf das Event "SessionAuthenticatedEvent", welches ausgelöst wird, wenn sich ein Spieler anmeldet, hören und darauf mit einer Nachricht reagieren:

onEvent: function(event) {
	event.getSession().sendNotification(NotifyType.MULTI_ALERT, "Willkommen im Hotel!");
}

Registriert werden kann ein Event-Listener auf zwei Arten. Mittels direkter Referenzierung der Klasse des Events, z. B. "SessionAuthenticatedEvent.class" oder der Referenzierung über den Klassennamen als String, z. B. "'SessionAuthenticatedEvent'".

Dazu gibt es die Funktionen IDK.addEventListener(Class<? extends Event> eventClass, Object obj) und IDK.addEventListener(String eventName, Object obj).

function initializePlugin() {
	IDK.addEventListener(SessionAuthenticatedEvent.class, PlayerLoginEvent);
	IDK.addEventListener("ItemPurchaseEvent", ItemPurchaseEvent);
}

Asynchrone Funktionen werden dann benötigt, wenn etwas eine gewisse Ladezeit beansprucht. Dies kann das Laden einer Webseite sein oder das Eintragen von Daten in eine MySQL Datenbank. Würde man deartige Vorgänge direkt ausführen, könnte dies eine Verzögerung im Hotelgeschehen verursachen. So wäre es möglich, dass z. B. die Chat-Nachricht eines Spielers verspätet ankommt. Asynchron bedeutet mehr oder weniger nebenläufig, es wird also neben anderen Funktionen "gleichzeitig" ausgeführt.

Das Ausführen von asynchronen Funktionen ist relativ einfach, da die Funktion dafür, "IDK.runAsyncTask(Object runnable)", nicht mehr benötigt als ein Objekt mit einer "run" Methode.

print("Ich werde als erstes angezeigt!");

IDK.runAsyncTask({
	run: function() {
		// Daten aus dem Internet laden..
		// Sie in der IDK Datenbank abspeichern..
		
		print("Ich werde als letztes angezeigt!");
	}
});

print("Ich werde als zweites angezeigt!");

Nachdem diese Einleitung verinnerlicht und verstanden wurde, die Code-Beispiele fleißig getestet wurden und der Wille besteht, etwas neues zu erschaffen, kann es an die Entwicklung gehen, welche schon von Anfang an angestrebt wurde.

Die nächste Anlaufstelle sollte von nun an das Nachschlagewerk sein. Dort sind Methoden und Attribute von bestimmten Klassen, wie z. B. "RoomPlayer" und "Session", aufgelistet. Hier ist es dem Programmierer möglich, die Bausteine, welche dieser für seine kreativen Ideen benötigt, zu finden und einzusetzen.

Falls weitere Fragen bzw. Anmerkungen bestehen, können diese gerne an die oben genannten Twitter Profile versandt werden.