Vorwort
Ich werde den Beitrag noch erweitern, das ist jetzt alles nur das, was mir spontan in den Kopf kam.
list statt std::list
Es sollten die std::-Dinge verwendet werden statt der eigenen Implementierung (src/list.h). Ich weiß nicht, warum es die andere überhaupt gibt oder gab, aber sie hat schon zu dem ein oder anderen Problemchen geführt.
Parallelisierung
Momentan ist RttR weder parallelisiert noch wäre dies möglich, da statt einer Trennung viele Klassen direkt ineinander "verknotet" sind. Eine Parallelisierung ist sinnvoll, da im Moment pro Grafik-Frame maximal ein GF durchgeführt wird. Eine Entkoppelung des Ganzen würde dafür sorgen, dass man das Spiel wesentlich schneller laufen lassen könnte. Diejenigen, die das Spiel tatsächlich spielen, werden bestätigen können, dass eine niedrige FPS-Begrenzung nicht nur das Spiel an sich ausbremst, sondern auch die Bewegungen mit der Maus etc.
Die KI neigt auch dazu, das Spiel bei gewissen Suchen auszubremsen. Umgehen könnte man auch dies, indem die KI parallel zum eigentlichen Ablauf der Logik ihre Entscheidungen trifft, ganz wie der Mensch.
Aufgrund der Probleme mit NAT, die immer wieder auftreten, wäre es auch toll, einen dedizierten Server zu haben. Bei lokalen Spielen könnte man einen Server, NWF etc. ganz umgehen und so für ein flüssiges Spielerlebnis sorgen. Noch schöner wäre es dann natürlich, wenn die KI auf dem Server mitlaufen würde.
Modularisierung
Viele Werte sind in RttR einfach fest eingebaut. Dazu zählen nicht einmal unbedingt die Enums wie Nation, Species, GoodType, Job etc. und Konstanten wie z.B. MAX_PLAYERS, sondern auch die vielen Stellen, an denen wirklich feste Werte im Code stehen. Um nur mal ein Beispiel zu nennen:
Code:
nobBaseWarehouse::nobBaseWarehouse(const BuildingType type,const unsigned short x, const unsigned short y,const unsigned char player,const Nation nation)
: nobBaseMilitary(type,x,y,player,nation), fetch_double_protection(false), producinghelpers_event(em->AddEvent(this,PRODUCE_HELPERS_GF+RANDOM.Rand(__FILE__,__LINE__,obj_id,PRODUCE_HELPERS_RANDOM_GF),1)), recruiting_event(0),
empty_event(0), store_event(0)
{
// Reserve nullen
for(unsigned i = 0;i<5;++i)
reserve_soldiers_available[i] =
reserve_soldiers_claimed_visual[i] =
reserve_soldiers_claimed_real[i] = 0;
}
Die Deklarationen sind nicht besser, auch da ist die 5 fest im Code drin. Es wäre also in dem Fall z.B. ungeheuer aufwändig, einen neuen Soldaten-Rang hinzuzufügen.
Terrain sollte auch nicht, wie es jetzt überall passiert, nur nach einer ID unterschieden werden und da dann nicht mit festen Werten. Beispiele hierfür finden sich vor allem im TerrainRenderer, wo man sogar sowas wie
Code:
if(gwv->GetNode(x,y).t1 < 20)
findet. Wenn nun jemand auf die Idee käme, zusätzliches Terrain einfügen zu wollen, gäbe das eine Menge Probleme, mit denen niemand rechnet.
Gerade bei Terrain wäre es auch schön, wenn es Eigenschaften gäbe wie 'walkable', 'buildable', 'swimmable', ... Aufgrund dieser Eigenschaften könnte man dann entscheiden, was auf dem Terrain möglich ist und käme von IDs ganz weg. Man müsste lediglich die alten S2-Karten beim Laden entsprechend konvertieren.
Neben dem Terrain wäre es auch ganz toll, wenn Gebäude und Berufe modular wären und sich einer allgemeinen API bedienen könnten. Abhängigkeiten könnte man als Liste in die Module integrieren, so dass z.B. ein Bäcker nur gemeinsam mit einer Mühle, einem Bauernhof und einem Brunnen geladen werden kann. Oder etwas abstrakter: ein Gebäude kann nur dann geladen werden, wenn es Produzenten für die benötigte(n) Ware(n) gibt.
Addons
Die jetzigen Addons sind alles andere als schön. Auch hier wären Module schön, gegebenenfalls müsste man im Core die ein oder andere umstellbare Option einbauen, die man über ein Interface verändern kann.
Im Falle von Addons wie den Katapult-Grafiken könnte man einfach ein Katapult-Modul bauen, das entsprechende Einstellungen mit sich bringt. Zusätzliche Gebäude wie den Köhler, das Ausbildungslager etc. würde man direkt als Gebäude-Modul integrieren, das man optional laden kann.
Modernisierung der Formate
Es wäre meines Erachtens nach sinnvoller, die Originaldateien einmalig in ein gängiges Format (z.B. PNG, da lossless und komprimiert) konvertieren und dann damit zu arbeiten. Neue Grafiken könnten mithilfe einer Palette in modernen Formaten erstellt werden und die Konvertierungsprobleme entfielen. Es wäre wesentlich leichter für einen "normalen" Menschen, neue Grafiken, Landschaftsobjekte etc. zu erstellen.
Dasselbe gilt auch für das Map-Format, da das alte Format sehr unflexibel ist und das dynamische Hinzufügen neuer Objekte (z.B. eines Schatzes, den man in Missionen finden könnte) nicht erlaubt. Ein modernes Format könnte Deko-Objekte bzw. Skripting integrieren und somit Missionen ermöglichen.
Desktop-/Window-Manager
Der Window-Manager müsste komplett überarbeitet werden. Das Einbauen des Beobachtungsfensters hat mir gezeigt, dass es da einiges zu tun gibt. Im Spiel darf man z.B. gerne mal versuchen, das Beobachtungsfenster mittels Rechtsklick auf den Rahmen zu schließen. Das wird nicht funktionieren, da der Window-Manager beim Rechtsklick nicht zwischen Rahmen und Fensterinhalt unterscheidet. In dem Zusammenhang wäre es auch toll, zumindest eine Stencil Maske zu verwenden, um dort, wo Fenster sind, nicht dennoch alles andere zu rendern.
Renderer
Abgesehen von den oben bereits angesprochenen Unzulänglichkeiten im Quellcode besteht zusätzlich das Problem, dass viele Funktionen von OpenGL noch verwendet werden, die seit vielen Jahren als
deprecated markiert wurden, also als abgelehnt bzw. überholt.
Im Moment wird immer alles gezeichnet, angefangen vom Terrain über die Terrain-Übergänge, Wege, Landschaftsobjekte, Gebäude, Figuren, Fenster, Mauszeiger, ..., was nicht nur äußerst ineffizient (Fill-Rate!) ist, sondern auch genau die falsche Reihenfolge. Eigentlich müsste man oben anfangen (erst Mauszeiger, dann Fenster etc. und zuletzt die Lücken mit dem Terrain füllen) und dafür sorgen, dass möglichst kein Pixel doppelt angefasst wird. Man wird es nicht unbedingt immer vermeiden (z.B. im Falle von durchscheindenen Grafiken wie Schatten), aber zumindest auf ein notwendiges Minimum reduzieren können.
Es bringt wenig, den Support für alte Grafikkarten aufrecht zu erhalten, wenn das Spiel darauf dennoch unspielbar langsam wird.
Überläufe, implizite Typen-Konvertierung etc.
Es sollten für Objekteigenschaften grundsätzlich Integer-Typen fester Größe genutzt werden, damit es nicht bei 32-Bit zum Überlauf kommt, aber 64-Bit weiterläuft. Auch Array-Indizes sollten an kritischen Stellen nicht gerade ein 'char' sein.
Es sollte außerdem vermieden werden, dass bei Aufrufen (insbesondere im Pathfinding) etliche Male implizite Typen-Konvertierungen zwischen x verschiedenen Integer-Typen stattfinden. Abgesehen davon, dass die Ausführung hierdurch verlangsamt wird, führt das zu potentiellen Fehlern und Async-Quellen bei 32- und 64-Bit.
Einige nette Links hierzu:
- http://www.viva64.com/en/a/0004/
- http://www.viva64.com/en/a/0050/
Außerdem sollten wir uns grundsätzlich ein paar Gedanken über die Effizienz des geschriebenen Codes machen:
- http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_Boston_01_304_paper.pdf
- http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_San_Jose_98_401_paper.pdf
- http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_SF_02_405_&_445_paper.pdf
Man müsste sich zusätzlich noch Wege überlegen, wie man verhindern kann, dass z.B. ein Objekt stirbt, aber Events hinterlässt, die dann mit nicht-initialisiertem Speicher durchgeführt werden.
Vorschläge für einen Rewrite bzw. ein neues Design
Hier ein kleiner Entwurf:
https://dl.dropboxusercontent.com/u/11244516/design.pdf
Der Server ist im Grunde dumm und macht nicht viel mehr als die GameCommands zu verteilen, die er erhält. Der Server in Kombination mit der Simulation sorgt für ein Interface, das für alle exakt gleich ist, egal auf welchem Client sie laufen. Es ist
essentiell, dass die Player die Simulation nie direkt ändern können, sondern immer über den Server gehen müssen. So wird schon per Design sichergestellt, dass das Spiel überall synchron läuft. Ob nun an einer Simulation ein menschlicher Spieler, ein Zuschauer, eine KI oder alle drei hängen, ist im Grunde egal. Damit wäre es sogar möglich, eine Simulation an einen dedizierten Server zu hängen, an der wiederum einige KIs hängen. Damit wäre es aber auch möglich, einen Server mit einer KI zu starten und mehrere KIs mit Simulation über das Netzwerk dranzuhängen, um eine Art Testing gegen Asyncs oder für die KI zu bauen. Als Zuschauer könnte man sich jederzeit einklinken und beobachten, was im Spiel passiert.
Eine Möglichkeit, im Async-Fall den kompletten Status erneut abzugleichen, wäre toll, braucht allerdings viel Bandbreite und bedingt, dass man einen schönen Weg hat, das Chaos erst mal aufzuräumen. Momentan gibt es auch da Probleme, so dass nicht alles sauber entsorgt wird, wenn man ein Spiel beendet.
Klar, das ist alles noch unvollständig und es gibt noch keine Implementierungen, aber ich denke, dass wir hiermit einen Weg gehen, der etwas für die Zukunft ist. Die aktuelle Nightly ist aus meiner Sicht aufgrund der vielen Async-Fälle nur im Singleplayer sinnvoll spielbar und es müsste zumindest eine Art Feature-Stop geben, bis man die Ursache für den Async gefunden hat. Letzteres erachte ich aber beim aktuellen Design als eine sehr schwere Aufgabe...