ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/docs/pws2007/cfplus.pod
Revision: 1.2
Committed: Sat Jul 18 05:59:11 2009 UTC (17 years, 2 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +1 -1 lines
Log Message:
riddify us of meta.yml garbage in manifest

File Contents

# User Rev Content
1 root 1.1 =encoding utf-8
2    
3     =head1 Crossfire+ - ein graphisches MORPG mit Perl
4    
5     Crossfire+ ist ein stark von Nethack inspiriertes, graphisches
6     Multiplayer-Online-RPG und gehört in die (sehr kleine) Klasse von
7     Echtzeit-Multiplayer-Roguelike-Spielen. Im Gegensatz zu den meisten
8     Roguelikes besitzt es nicht nur zufällig generierte Karten sondern auch
9 root 1.2 über 5000 handgemachte Karten mit Rätseln und Quests.
10 root 1.1
11     =begin latex
12    
13     \begin{center}
14     \includegraphics{cfplus/scr2.eps}
15     \end{center}
16    
17     =end latex
18    
19     =head2 Entstehung
20    
21     Crossfire+ ist aus Crossfire entstanden, das wiederum um 1994 entstanden
22     ist. Um 2005 waren jedoch die meisten der Originalentwickler verschwunden,
23     der Server crashte bei Benutzung täglich und die übriggebliebenen
24     Maintainer waren nicht in der Lage, vorhandene Probleme zu lösen. Daher
25     wurde 2005 Crossfire+ gegründet, ein Fork, der das Ziel hat, den (nicht
26     mehr zeitgemäßen) Programmcode zu modernisieren und stabiler zu machen.
27    
28     =head2 Features
29    
30     Crossfire+ ist im Gegensatz zu Crossfire in C++ geschrieben und
31     wesentliche Teile des Servers sind in Perl gehalten, was der Stabilität
32     und der Erweiterbarkeit sehr zugute gekommen ist. So fängt Crossfire
33     unter Last vieler Spieler an zu ruckeln, da es Karten synchron nachlädt,
34     während Crossfire+ Laden und Speichern aller Daten im Hintergrund
35     vornimmt (dank IO::AIO und Coro mit vertretbarem Aufwand). Das verhindert
36     Ruckler, und dank des neugeschriebenen Parsers ist das Laden bis zu
37     fünffach schneller.
38    
39     Doch auch das Objektsystem wurde stark verbessert und vor allem mit Perl
40     kombiniert: An Karten, Monster, Spieler und viele andere Objekte lassen
41     sich Perl-Erweiterungen binden die viele Details des Verhaltens regeln
42     können.
43    
44     Zeitgleich wurde ein auf OpenGL-basierender und in Perl gschriebener
45     neuer Client geschaffen (es gibt derzeit ca. 7 verschiedene Clients für
46     unterschiedlichste Platformen). Um platformunabhägig zu bleiben, musste
47     ein eigenes OpenGL-Toolkit implementiert werden.
48    
49     Die Welt von Crossfire+, von oben :)
50    
51     =begin latex
52    
53     \begin{center}
54     \includegraphics{cfplus/world.eps}
55     \end{center}
56    
57     =end latex
58    
59     =head2 Der Client
60    
61     Scorn bei Nacht, in CFPlus:
62    
63     =begin latex
64    
65     \begin{center}
66     \includegraphics{cfplus/scr1.eps}
67     \end{center}
68    
69     =end latex
70    
71     Wie schon erwähnt, wurde der Client größtenteils in Perl
72     geschrieben. Ausnahmen sind der SDL- und SDL_Mixer-Code sowie
73     die Schnitstelle zu OpenGL und einige Hilfsfunktionen (wie ein
74     Pango-OpenGL-Renderer).
75    
76     Die Wahl fiel auf SDL um Platformunabhängigkeit zu erreichen. Leider kann
77     man SDL_Perl seit Umstellung auf Module::Build wie viele andere derartige
78     Module nicht mehr auf nicht-Unix-System zum Laufen bringen, weshalb wir
79     gezwungen waren, unseren eigenen SDL-Schnitstellencode zu schreiben, was
80     uzm Glück sehr einfach war, da´ wir SDL nur benutzen um ein Fenster mit
81     OpenGL-Fähigkeit zu erhalten und dannach alles über OpenGL abwickeln. Die XS-Schnittstelle für
82     OpenGL ist sehr dünn, selbst das Texturformat wird von Perl aufbereitet und einem direkten glTexImage-Aufruf übergeben:
83    
84     glTexImage2D GL_TEXTURE_2D, 0,
85     $self->{internalformat},
86     $tw, $th,
87     0,
88     $self->{format},
89     $self->{type},
90     $data;
91    
92     Das Toolkit selbst ist ebenfalls in Perl geschrieben und lehnt sich im
93     Design an Toolkits wie Gtk+ oder Tk an. Um den transparenten unteren Teil
94     des Clients (mit "Floorbox" und HP/Mana- etc. Pegel) zu erzeugen, wird
95     z.B. folgender Code verwendet:
96    
97     sub make_gauge_window {
98     my $win = new CFPlus::UI::Frame;
99    
100     $win->add (my $hbox = new CFPlus::UI::HBox
101     children => [
102     (new CFPlus::UI::HBox expand => 1),
103     (new CFPlus::UI::VBox children => [
104     (new CFPlus::UI::Empty expand => 1),
105     (new CFPlus::UI::Frame bg => [0, 0, 0, 0.4],
106     child => new CFPlus::UI::Table),
107     ]),
108     (my $vbox = new CFPlus::UI::VBox),
109     ],
110     );
111    
112     $vbox->add (new CFPlus::UI::HBox
113     expand => 1,
114     children => [
115     (new CFPlus::UI::Empty expand => 1),
116     (my $hb = new CFPlus::UI::HBox),
117     ],
118     );
119    
120     $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health");
121     $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana");
122     $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace");
123     $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food");
124    
125     $vbox->add (my $exp = new CFPlus::UI::Label
126     valign => 0, align => 1, can_hover => 1,
127     can_events => 1, tooltip => "#stat_exp");
128     $vbox->add (my $rng = new CFPlus::UI::Label
129     valign => 0, align => 1, can_hover => 1,
130     can_events => 1, tooltip => "#stat_ranged");
131     }
132    
133     Eingabefelder fuktionieren ähnlich einfach:
134    
135     $vbox->add (
136     $HOST_ENTRY = new CFPlus::UI::Entry
137     expand => 1,
138     text => $CFG->{profile}{default}{host},
139     tooltip => "The hostname of the server to connect to",
140     on_changed => sub {
141     my ($self, $value) = @_;
142     $CFG->{profile}{default}{host} = $value;
143     0
144     }
145     );
146    
147     Widgets selbst lassen sich in Perl erstaunlich kompakt realisieren. Als
148     nichtriviales Beispiel soll hier kurz der gesamte Code für das
149     Checkbox-Widget gezeigt werden:
150    
151     =begin latex
152    
153     \begin{center}
154     \includegraphics{cfplus/checkbox.eps}
155     \end{center}
156    
157     =end latex
158    
159     package CFPlus::UI::CheckBox;
160    
161     Die C<DrawBG>-Basisklasse zeichnet lediglich einen (optional
162     transparenten) Hintergrund und wird von vielen Widgets benutzt um einen
163     definierten Untergrund zu erhalten:
164    
165     our @ISA = CFPlus::UI::DrawBG::;
166    
167     Hier werden die Texturen geladen, es gibt derer zwei, für aktiv/nicht
168     aktiv:
169    
170     my @tex =
171     map { new_from_file CFPlus::Texture CFPlus::find_rcfile $_, mipmap => 1 }
172     qw(c1_checkbox_bg.png c1_checkbox_active.png);
173    
174     use CFPlus::OpenGL;
175    
176     Der Konstruktor erwartet Key-Value-Paare und setzt die Defaults:
177    
178     sub new {
179     my $class = shift;
180    
181     $class->SUPER::new (
182     padding_x => 2,
183     padding_y => 2,
184     fg => [1, 1, 1],
185     active_fg => [1, 1, 0],
186     bg => [0, 0, 0, 0.2],
187     active_bg => [1, 1, 1, 0.5],
188     state => 0,
189     can_hover => 1,
190     @_
191     )
192     }
193    
194     C<size_request> wird vom Layout-Algorithmus benutzt um die Wunschgröße
195     eines Widgets in Erfahrung zu bringen. Checkboxen sind typischerweise
196     unproblematisch, daher wird immer 6x6 angefordert.
197    
198     sub size_request {
199     my ($self) = @_;
200    
201     (6) x 2
202     }
203    
204     C<toggle> ist eine Hilfsfunktion, mit der man die Checkbox togglen kann
205     bzw. die bei einer Änderung ein Signal auslöst an das man sich binden
206     kann. C<update> sorgt dafür, daß das gesamte Fenster neugezeichnet
207     wird. OpenGL arbeitet indem es bei jeder Änderung den kompletten
208     Schirm neu aufbaut. Anders als z.B. ego-shooter rendert CFPlus nicht so
209     schnell die Hardware und Software es hergeben, sondern nur dann, wenn es
210     wirklich Änderungen gab (CFPlus läuft bei uns den ganzen Tag, da ist es
211     hilfreich, wenn es wenig Rechenzeit verbraucht :).
212    
213     sub toggle {
214     my ($self) = @_;
215    
216     $self->{state} = !$self->{state};
217     $self->emit (changed => $self->{state});
218     $self->update;
219     }
220    
221     Durch C<invoke_>-Methoden werden Eingabevents an die Widgets gereicht,
222     hier der C<button_down>-Event, mit dme man die Checkbox togglen kann. Die
223     Methode prüft, ob der Klick im richtigen Bereich liegt und ruft
224     gegebenenfalls C<toggle> auf:
225    
226     sub invoke_button_down {
227     my ($self, $ev, $x, $y) = @_;
228    
229     if ($x >= $self->{padding_x} && $x < $self->{w} - $self->{padding_x}
230     && $y >= $self->{padding_y} && $y < $self->{h} - $self->{padding_y}) {
231     $self->toggle;
232     } else {
233     return 0
234     }
235    
236     1
237     }
238    
239     Die C<_draw>-Methode muss das Widget zeichnen, und zwar bei jedem Aufruf
240     komplett. Diese Implementation ruft zuerst die SUPEr-Methode auf (von
241     DrawBG) und benutzt dann C<draw_quad_alpha>, eine Hilfsfunktion, die eine
242     häufig benutze Folge von OpenGL-Aufrufen (glBlendFunc, einige glEnable,
243     glAlphaFunc, glBindTexture, glBegin, glTexCoord, glVertex usf.) in C
244     implementiert, was (hoffentlich) einen Geschwindigkeitsvorteil bringt
245     (Funktionsaufrufe sind in Perl vergleichsweise kostpielig) aber vor allem
246     den immer wiederkehrenden "mal mir die Textur dorthin"-Vorgang verkürzt
247     (draw_quad_alpha wird allein im Toolkit-Code 13 mal aufgerufen).
248    
249     Je nachdem, ob die Checkbox gerade den Mouse-Focus hat oder nicht wird
250     ausserdem mit einer anderen Farbe gemischt:
251    
252     sub _draw {
253     my ($self) = @_;
254    
255     $self->SUPER::_draw;
256    
257     glTranslate $self->{padding_x} + 0.375, $self->{padding_y} + 0.375, 0;
258    
259     my ($w, $h) = @$self{qw(w h)};
260     my $s = List::Util::min $w - $self->{padding_x} * 2, $h - $self->{padding_y} * 2;
261    
262     glColor @{ $FOCUS == $self ? $self->{active_fg} : $self->{fg} };
263     my $tex = $self->{state} ? $tex[1] : $tex[0];
264     glEnable GL_TEXTURE_2D;
265     $tex->draw_quad_alpha (0, 0, $s, $s);
266     glDisable GL_TEXTURE_2D;
267     }
268    
269     Der gesamte Toolkit-Code umfaßt zur Zeit 4000 Zeilen, bietet aber so
270     komplexe Widgets wie einen Texteditor, Notebooks oder einen Pod-Viewer mit
271     Bild- und Hyperlinkfähigkeit.
272    
273     =head2 Der Server
274    
275     Der Server ist, wie eingangs erwähnt, im Kern in C++ geschrieben. Der
276     C++-Teil kümmert sich um die meisten zeitkritischen Aufgaben wie
277     Wegefindung und Monsterbewegung (bei 10 Spielern sind im Schnitt ca. 20000
278     Objekte aktiv, die alle 120ms aufmerksamkeit erfordern, der Server
279     verträgt momentan ca. 200000 aktive Objekte).
280    
281     Alles "höherwertige" wie die meisten Kommunikationskommandos und
282     Administratives wie Login sind in Perl implementiert, wieso auch das Laden
283     und Speichern von Dateien.
284    
285     =head3 Asynchrones Laden und Speichern
286    
287     Letzteres wurde zu einem Problem: lief im Hintergrund ein Backup stockte
288     der Server bei jedem Mapwechseln - bei 20 Spielern gleichzeitig also
289     andauernd. I/O in einen eigenen Thread zu verlegen schied aus, da zu viele
290     globale Datenstrukturen existierten und verändert werden müssten und der
291     Server-Code an allen Ecken und enden statische Variablen benutzte. Daher
292     verlegten wir uns auf eine Lösung mit IO::AIO für den eigentliche I/O
293     und Coro, um langwierige Vorgänge quasiparallel auszuführen ohne uns
294     um fine-grained Locking Gedanken machen zu müssen und trotzdem noch
295     rechtzeitig alle 120ms einen Update generieren zu können.
296    
297     Anders als Crossfire (bei dem alle Karten die bei einem Crash aktiv waren
298     verlorengehen) schreibt Crossfire+ Karten und Spielerdaten ca. alle 20
299     Sekunden auf die Festplatte (zusätzlich schreibt es auch bei Crashes im
300     allgemeinen alle Daten fehlerfrei auf die Festplatte bevor der Server sich
301     beendet).
302    
303     Wie so vieles, ist dies durch eine Server-Erweiterung realisiert, den
304     sogenannten C<map-scheduler>, die im wesentlichen aus einer Coroutine
305     besteht die regelmäßig Karten speichert bzw. ganz aus dem Speicher
306     löscht oder gar Karten zurücksetzt. C<async_ext> erzeugt eine Coroutine
307     die bei Entfernen oder Neuladen der Extension automatisch gelöscht wird:
308    
309     our $SCHEDULER = cf::async_ext {
310    
311     Der Scheduler läuft einmal all paar Sekunden, und das immer wieder:
312    
313     my $schedule_interval = Coro::Event->timer (after => 1, interval => $SCHEDULE_INTERVAL);
314     while () {
315     $schedule_interval->next;
316    
317     Iteriert dabei über alle Karten:
318    
319     my @maps = keys %cf::MAP;
320     for (@maps) {
321    
322     Möglicherweise wurde eine Karte schon gelöscht (zwischen dem keys-Aufruf
323     und dem benutzen der Keys können viele Sekunden liegen):
324    
325     my $map = $cf::MAP{$_}
326     or next;
327     $map->valid or next;
328    
329     Falls etwas schiefgeht, logge den Fehler statt den (sehr wichtigen) Scheduler zu beenden:
330    
331     eval {
332    
333     Karten werden regelmäßig auf ihren Ursprungszustand zurückgesetzt,
334     sonst gäbe es bald keine Monster zum töten mehr:
335    
336     if ($map->should_reset) {
337     $map->reset;
338    
339     Wenn die Karte im Speicher ist, länger nicht benutzt wurde und keine
340     Spieler auf ihr sind, speichere sie und lösche sie aus dem Speicher um
341     die Last auf den Server zu verringern:
342    
343     } elsif ($map->in_memory == cf::MAP_IN_MEMORY) {
344     if ($map->last_access + $SWAP_TIMEOUT <= $cf::RUNTIME && !$map->players) {
345     $map->swap_out;
346    
347     Ansonsten speichere die Map einfach alle $SAVE_TIMEOUT Sekunden:
348    
349     } elsif ($map->{last_save} + $SAVE_TIMEOUT <= $cf::RUNTIME) {
350     $map->save;
351     }
352     }
353     };
354     warn $@ if $@;
355    
356     Gib anderen Teilen des Servers Rechenzeit (dies ist sehr wahrscheinlich
357     unnötig da dies beim Speichern automatisch passiert und das iterieren
358     durch alle Maps sehr wahrscheinlich nie lange dauert, aber es schadet
359     nicht):
360    
361     Coro::cede;
362     };
363     }
364     };
365    
366     =head3 Objekte steuern
367    
368     Eines der wenigen neuen Features von Crossfire aus jüngerer Zeit
369     sind Transporte: Objekte, die von Spielern gesteuert werden können
370     (z.B. Schiffe). Die Implementation in Crossfire bestand aus einem 23kB
371     großem Patch an vielen Dateien und führte erwartunsggemäß alle
372     paar Tage zu einem crash, weshalb er in Crossfire+ durch eine (weniger
373     featurevolles dafür aber stabiles) Extension ersetzt wurde.
374    
375     Um eine Extension an ein ein Objekt zu binden, setzt man auf den
376     "Archetyp" des Objektes (das ist so etwas wie das ursprüngliche Objekt
377     von dem Kopien erstellt werden) ein C<attach>-Attribut, oder bindet sich
378     gleich an alle Objekte einer bestimmten Klasse (z.b. C<TRANSPORT>), wie
379     hier:
380    
381     cf::object->attach (
382     type => cf::TRANSPORT,
383     on_apply => sub {
384     my ($tr, $ob) = @_;
385    
386     return unless $ob->type == cf::PLAYER;
387    
388     if ($ob->contr->attached ("transport_player_steer")) {
389     $ob->message ("You stop steering the " . $tr->name . ".");
390     $ob->contr->detach ("transport_player_steer");
391     } else {
392     $ob->message ("You now steer the " . $tr->name . ".");
393     $ob->contr->attach ("transport_player_steer");
394     }
395    
396     cf::override;
397     },
398     );
399    
400     Der obige Aufruf von C<attach> bindet sich auf den B<appl>-Event aller
401     Objekte vom Typ C<TRANSPORT>. Beim ersten Apply wird dynamisch eine
402     weitere Erweiterung gebunden, "transport_player_steer" die den Spieler
403     selbst bewegungsunfähig macht und statt dem Spielerobjekte den Transport
404     (z.B. das Schiff) bewegt.
405    
406     Extensions können einen Namen erhalten auf die man sich z.B. beim
407     Erstelle von Karten und neuen Objekten wie Monstern beziehen kann. Die
408     "transport_player_steer"-Erweiterung selbst ist ganz einfach:
409    
410     cf::player::attachment transport_player_steer =>
411     on_move => sub {
412     my ($pl, $dir) = @_;
413    
414     my $ob = $pl->ob;
415    
416     if (my $tr = find_transport $pl) {
417     my @ontop;
418    
419     for (my $tr = $tr; $tr; $tr = $tr->more) {
420     for (my $ob = $tr->above; $ob; $ob = $ob->above) {
421     push @ontop, $ob;
422     }
423     }
424    
425     if ($tr->move ($dir, $ob)) {
426     # do multiple loops in case some player/item blocks another
427     # do not endlessly loop as the server is far too broken, e.g.
428     # you can drop floors on top of non-floors etc.
429     for (1..50) {
430     @ontop or last;
431     @ontop = map $_->move ($dir, $_) ? () : $_, @ontop;
432     }
433     }
434    
435     cf::override;
436     }
437     },
438     ;
439    
440     Sie fängt nur einen Event ab: B<move>, der aufgerufen wird, wenn der
441     Spieler ein Bewegungskommando auslöst. Sie sucht den Transport der zum
442     Spieler gehört und merkt sich, was alles "auf" dem Transport liegt, in
443     @ontop.
444    
445     Danach versucht es den Transport zu bewegen und, falls dies klappt
446     (ein Schiff kann nicht auf Land segeln, zumindest nicht überall
447     :), auch alle @ontop-Objekte. Da sich diese im Weg sein können,
448     müsste man sie entwder sortieren oder, wie hier, man versucht es
449     so lange bis sich alle erfolgreich bewegt haben (oder bis zu 50
450     Iterationen - lieber verlieren wir ein paar Objekte als den Server zu
451     freezen. Echtzeit-Spiele-Server verlangen grundsätzlich nach anderen
452     Herangehensweisen für Stabilitätsprobleme...)
453    
454     =head2 Links
455    
456     Die Crossfire+-Homepage:
457    
458     http://cf.schmorp.de/
459    
460     Der CFPlus-Client:
461    
462     http://cf.schmorp.de/client.shtml
463    
464     Alle Karten von Crossfire+, online:
465    
466     http://cfmaps.schmorp.de/
467    
468     Die Crossfire-Homepage, mit Handbuch und vielen Hintegrundmaterialien:
469    
470     http://crossfire.real-time.com/
471    
472     =head2 Autor
473    
474     Marc Lehmann <crossfire@schmorp.de>
475    
476