ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.192
Committed: Fri Apr 28 18:06:13 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.191: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2 root 1.25
3 root 1.2 use strict;
4 root 1.25 use utf8;
5 root 1.2
6 root 1.176 BEGIN {
7     if (%PAR::LibCache) {
8     @INC = grep ref, @INC; # weed out all paths except pars loader refs
9    
10     while (my ($filename, $zip) = each %PAR::LibCache) {
11     for ($zip->memberNames) {
12     next unless /^\/root\/(.*)/;
13     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
14     unless -e "$ENV{PAR_TEMP}/$1";
15     }
16     }
17    
18     unshift @INC, $ENV{PAR_TEMP};
19    
20     if ($^O eq "MSWin32") {
21     $ENV{GTK_RC_FILES} = "$ENV{PAR_TEMP}/share/themes/MS-Windows/gtk-2.0/gtkrc";
22     }
23     }
24     }
25    
26     # need to do it again because that pile of garbage called PAR nukes it before main
27     unshift @INC, $ENV{PAR_TEMP};
28    
29 root 1.87 use Time::HiRes 'time';
30     use Event;
31 root 1.13
32 elmex 1.11 use Crossfire;
33 root 1.2 use Crossfire::Protocol;
34    
35 root 1.116 use Compress::LZF;
36    
37 root 1.67 use CFClient;
38 root 1.72 use CFClient::UI;
39 root 1.141 use CFClient::MapWidget;
40 elmex 1.10
41 root 1.177 $Event::DIED = sub {
42     CFClient::error $_[1];
43     };
44 root 1.176
45 root 1.178 #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
46    
47 root 1.63 our $VERSION = '0.1';
48    
49 root 1.96 my $MAX_FPS = 60;
50 root 1.90 my $MIN_FPS = 5; # unused as of yet
51 root 1.63
52 root 1.112 our $META_SERVER = "crossfire.real-time.com:13326";
53    
54 root 1.116 our $FACEMAP;
55     our $TILECACHE;
56     our $MAPCACHE;
57 root 1.19
58 root 1.87 our $LAST_REFRESH;
59     our $NOW;
60    
61 elmex 1.10 our $CFG;
62 root 1.13 our $CONN;
63 root 1.85 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
64 root 1.2
65 root 1.75 our @SDL_MODES;
66 root 1.30 our $WIDTH;
67     our $HEIGHT;
68     our $FULLSCREEN;
69 root 1.99 our $FONTSIZE;
70 root 1.30
71 root 1.168 our $FONT_PROP;
72     our $FONT_FIXED;
73    
74 root 1.95 our $MAP;
75 root 1.187 our $MAPMAP;
76 root 1.69 our $MAPWIDGET;
77 root 1.112 our $BUTTONBAR;
78     our $LOGVIEW;
79     our $CONSOLE;
80     our $METASERVER;
81 root 1.57
82 root 1.173 our $FLOORBOX;
83 elmex 1.125 our $GAUGES;
84 elmex 1.154 our $STATWIDS;
85 elmex 1.125
86 root 1.86 our $SDL_ACTIVE;
87 root 1.13 our %SDL_CB;
88 root 1.18
89 root 1.134 our $SDL_MIXER;
90     our @SOUNDS; # event => file mapping
91     our %AUDIO_CHUNKS; # audio files
92    
93 root 1.30 our $ALT_ENTER_MESSAGE;
94 root 1.51 our $STATUS_LINE;
95 root 1.64 our $DEBUG_STATUS;
96 root 1.30
97 elmex 1.191 our $INVWIN;
98     our $INV;
99    
100 root 1.82 sub status {
101     $STATUS_LINE->set_text ($_[0]);
102 root 1.128 $STATUS_LINE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h} - $STATUS_LINE->{h});
103 root 1.82 }
104    
105     sub debug {
106     $DEBUG_STATUS->set_text ($_[0]);
107 root 1.128 $DEBUG_STATUS->move ($WIDTH - $DEBUG_STATUS->{w}, 0, $DEBUG_STATUS->{w}, $DEBUG_STATUS->{h});
108 root 1.82 }
109    
110 root 1.84 sub start_game {
111 root 1.85 status "logging in...";
112    
113 root 1.106 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
114 root 1.84
115 root 1.116 $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
116    
117 root 1.95 $MAP = new CFClient::Map $mapsize, $mapsize;
118 root 1.112
119     my ($host, $port) = split /:/, $CFG->{host};
120 root 1.95
121 root 1.84 $CONN = new conn
122 root 1.112 host => $host,
123     port => $port || 13327,
124 root 1.84 user => $CFG->{user},
125     pass => $CFG->{password},
126     mapw => $mapsize,
127     maph => $mapsize,
128     ;
129    
130 root 1.85 status "login successful";
131    
132 root 1.84 CFClient::lowdelay fileno $CONN->{fh};
133     }
134    
135     sub stop_game {
136     undef $CONN;
137     }
138    
139 root 1.111 sub client_setup {
140 root 1.99 my $dialog = new CFClient::UI::FancyFrame
141 root 1.150 title => "Client Setup",
142 root 1.81 child => (my $vbox = new CFClient::UI::VBox);
143     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
144    
145 root 1.140 $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
146 root 1.81 $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
147    
148     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, scalar @SDL_MODES, 1]);
149 root 1.150 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
150 root 1.81
151     $mode_slider->connect (changed => sub {
152     my ($self, $value) = @_;
153    
154     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
155     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
156     });
157     $mode_slider->emit (changed => $mode_slider->{range}[0]);
158 root 1.82
159 elmex 1.158 my $row = 1;
160    
161     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
162 elmex 1.166 $table->add (1, $row++, new CFClient::UI::CheckBox
163     state => $CFG->{fullscreen},
164     tooltip => "Bring the client into fullscreen mode",
165     connect_changed => sub {
166     my ($self, $value) = @_;
167     $CFG->{fullscreen} = $value;
168     }
169     );
170 root 1.85
171 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
172 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
173     state => $CFG->{fast},
174     tooltip => "Lower the visual quality considerably to speed up rendering.",
175     connect_changed => sub {
176     my ($self, $value) = @_;
177     $CFG->{fast} = $value;
178     }
179     );
180 root 1.89
181 root 1.169 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
182     $table->add (1, $row++, new CFClient::UI::Slider
183     range => [$CFG->{map_scale}, 0.25, 2, 0.05],
184     tooltip => "Enlarge or shrink the displayed map",
185     connect_changed => sub {
186     my ($self, $value) = @_;
187     $CFG->{map_scale} = 0.05 * int $value / 0.05;
188     }
189     );
190    
191 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
192 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
193     state => $CFG->{fow_enable},
194     tooltip => "Fog-of-War marks areas that cannot be seen by the player",
195     connect_changed => sub {
196     my ($self, $value) = @_;
197     $CFG->{fow_enable} = $value;
198     }
199     );
200 root 1.97
201 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
202 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
203     range => [$CFG->{fow_intensity}, 0, 1 + 0.001, 0.001],
204     tooltip => "The higher the intensity, the lighter the Fog-of-War color",
205     connect_changed => sub {
206     my ($self, $value) = @_;
207     $CFG->{fow_intensity} = $value;
208     }
209     );
210 root 1.90
211 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
212 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
213     state => $CFG->{fow_smooth},
214     tooltip => "Smooth the Fog-of-War a bit to make it more realistic",
215     connect_changed => sub {
216     my ($self, $value) = @_;
217     $CFG->{fow_smooth} = $value;
218     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
219     }
220     );
221 root 1.91
222 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
223 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
224     range => [$CFG->{gui_fontsize}, 0.5, 2, 0.1],
225     tooltip => "The font size used by most GUI elements",
226     connect_changed => sub {
227     $CFG->{gui_fontsize} = 0.1 * int $_[1] * 10;
228 root 1.140 # $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
229 root 1.163 }
230     );
231 root 1.140
232 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Server Log Fontsize");
233 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
234     range => [$CFG->{log_fontsize}, 0.5, 2, 0.1],
235     tooltip => "The font size used by the server log window only",
236     connect_changed => sub {
237     $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = 0.1 * int $_[1] * 10);
238     }
239     );
240 root 1.105
241 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
242 root 1.163
243     $table->add (1, $row++, new CFClient::UI::Slider
244     range => [$CFG->{stat_fontsize}, 0.5, 2, 0.1],
245     tooltip => "The font size used by the statistics window only",
246     connect_changed => sub {
247     $CFG->{stat_fontsize} = 0.1 * int $_[1] * 10;
248     &set_stats_window_fontsize;
249     }
250     );
251 elmex 1.157
252 root 1.163 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
253     $table->add (1, $row++, new CFClient::UI::Slider
254     range => [$CFG->{gauge_size}, 0.2, 0.8, 0.02],
255     tooltip => "Adjust the size of the stats gauges at the bottom right",
256     connect_changed => sub {
257     $CFG->{gauge_size} = $_[1];
258 root 1.164 my $h = int $HEIGHT * $CFG->{gauge_size};
259 root 1.163 $GAUGES->{win}->set_size ($WIDTH, $h);
260 root 1.164 $GAUGES->{win}->move (0, $HEIGHT - $h);
261 root 1.163 }
262     );
263 elmex 1.158
264     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
265 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
266     range => [$CFG->{gauge_fontsize}, 0.5, 2.0, 0.1],
267 elmex 1.166 tooltip => "Adjusts the fontsize of the gauges at the bottom right",
268 root 1.163 connect_changed => sub {
269     $CFG->{gauge_fontsize} = 0.1 * int $_[1] * 10;
270     &set_gauge_window_fontsize;
271     }
272     );
273 elmex 1.158
274 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
275     expand => 1, align => 0, text => "Apply",
276 root 1.168 tooltip => "Apply the video settings",
277 root 1.163 connect_activate => sub {
278     video_shutdown ();
279     video_init ();
280     }
281     );
282 root 1.111
283 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
284 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
285     state => $CFG->{audio_enable},
286     tooltip => "If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
287     connect_changed => sub {
288     $CFG->{audio_enable} = $_[1];
289     }
290     );
291 root 1.140 # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
292     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
293     # $CFG->{effects_volume} = $_[1];
294     # });
295 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
296     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
297 root 1.163 $hbox->add (new CFClient::UI::CheckBox
298     expand => 1, state => $CFG->{bgm_enable},
299     tooltip => "Enable background music playing",
300     connect_changed => sub {
301     $CFG->{bgm_enable} = $_[1];
302     }
303     );
304     $hbox->add (new CFClient::UI::Slider
305     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0.1],
306     tooltip => "The volume of the background music",
307     connect_changed => sub {
308     $CFG->{bgm_volume} = $_[1];
309     CFClient::MixMusic::volume $_[1] * 128;
310     }
311     );
312 root 1.140
313 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
314     expand => 1, align => 0, text => "Apply",
315 root 1.168 tooltip => "Apply the audio settings",
316 root 1.163 connect_activate => sub {
317     audio_shutdown ();
318     audio_init ();
319     }
320     );
321 elmex 1.137
322 elmex 1.188 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Communication cmd");
323     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
324     text => $CFG->{say_command},
325     tooltip => "This is the command that will be used if you write a line in the message window entry. "
326     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
327     ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
328     connect_changed => sub {
329     my ($self, $value) = @_;
330     $CFG->{say_command} = $value;
331     }
332     );
333    
334 root 1.111 $dialog
335     }
336    
337 elmex 1.157 sub set_stats_window_fontsize {
338 elmex 1.158 for (values %{$STATWIDS}) {
339 elmex 1.157 $_->set_fontsize ($::CFG->{stat_fontsize});
340     }
341     }
342    
343 elmex 1.158 sub set_gauge_window_fontsize {
344     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
345     $_->set_fontsize ($::CFG->{gauge_fontsize});
346     }
347 root 1.169
348     # local $GAUGES->{win}{parent};#d#
349     # use PApp::Util; open D, ">:utf8", "d"; print D PApp::Util::dumpval $GAUGES->{win}; close D;
350 elmex 1.158 }
351    
352     sub make_gauge_window {
353     my $gh = int ($HEIGHT * $CFG->{gauge_size});
354 elmex 1.161 # my $gw = int ($WIDTH * $CFG->{gauge_w_size});
355 elmex 1.158
356     my $win = new CFClient::UI::Frame (
357 root 1.169 y => $HEIGHT - $gh, x => 0, user_w => $WIDTH, user_h => $gh
358 elmex 1.158 );
359 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
360     children => [
361     (new CFClient::UI::HBox expand => 1),
362     ($FLOORBOX = new CFClient::UI::VBox),
363     (my $vbox = new CFClient::UI::VBox),
364     ],
365     );
366 elmex 1.158
367 root 1.173 $vbox->add (new CFClient::UI::HBox
368     expand => 1,
369     children => [
370     (new CFClient::UI::Empty expand => 1),
371     (my $hb = new CFClient::UI::HBox),
372     ],
373     );
374 elmex 1.161
375 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
376 root 1.181 tooltip => "Health points. Measures of how much damage you can take before dying. Hit points are determined from your level and are influenced by the value of your Con. Hp value may range between 1 to beyond 500 and higher values indicate a greater ability to withstand punishment.");
377 root 1.172 $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
378 root 1.181 tooltip => "Spell points. Measures of how much \"fuel\" you have for casting spells and incantations. Mana is calculated from your level and your Pow. Mana values can range between 1 to beyond 500 (glowing crystals can increase the current spell points beyond your normal maximum). Higher values indicate greater amounts of mana.");
379 root 1.172 $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
380 root 1.181 tooltip => "Grace points - how favored you are by your god. In game terms, how much divine magic you can cast. Your level, Wis and Pow effect what the value of grace is. Prayong on an altar of your god can increase this value beyond your normal maximum. Grace can take on large positive and negative values. Positive values indicate favor by the gods.");
381 root 1.172 $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
382 root 1.181 tooltip => "Food. Ranges between 0 (starving) and 999 (satiated). At a value of 0 the character begins to die. Some magic can speed up or slow down the character digestion. Healing wounds will speed up digestion too.");
383 root 1.172
384 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
385 root 1.182 tooltip => "Experience points and overall level - experience is increased as a reward for appropriate action (such as killing monsters) and may decrease as a result of a magical attack or dying. Level is directly derived from the experience value. As the level of the character increases, the character becomes able to succeed at more difficult tasks. A character's level starts at a value of 0 and may range up beyond 100.");
386 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
387 root 1.172 tooltip => "Ranged attack - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
388 elmex 1.158
389     $GAUGES = {
390 elmex 1.166 exp => $exp, win => $win, range => $rng,
391 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
392     };
393 root 1.169
394     &set_gauge_window_fontsize;
395    
396 elmex 1.158 $win
397     }
398    
399 elmex 1.154 sub make_stats_window {
400 root 1.183 my $tgw = new CFClient::UI::FancyFrame x => $WIDTH * 2/5, y => 0, title => "Stats";
401 root 1.155
402 root 1.185 $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
403 root 1.168 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1);
404     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1);
405 elmex 1.156
406     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
407    
408     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
409    
410 root 1.174 my $black = [0, 0, 0];
411    
412 root 1.180 for (
413     [0, 0, st_str => "Str", 30, "Physical Strength, determines damage dealt with weapons, how much you can carry, and how often you can attack"],
414     [0, 1, st_dex => "Dex", 30, "Dexterity, your physical agility. Determines chance of being hit and affects armor class and speed"],
415     [0, 2, st_con => "Con", 30, "Constitution, physical health and toughness. Determines how many healthpoints you can have"],
416     [0, 3, st_int => "Int", 30, "Intelligence, your ability to learn and use skills and incantations (both prayers and magic) and determines how much spell points you can have"],
417     [0, 4, st_wis => "Wis", 30, "Wisdom, the ability to learn and use divine magic (prayers). Determines how many grace points you can have"],
418     [0, 5, st_pow => "Pow", 30, "Power, your magical potential. Influences the strength of spell effects, and also how much your spell and grace points increase when leveling up"],
419     [0, 6, st_cha => "Cha", 30, "Charisma, how well you are received by NPCs. Affects buying and selling prices in shops."],
420    
421     [2, 0, st_wc => "Wc", -120, "Weapon Class, effectiveness of melee/missile attacks. Lower is more potent. Current weapon, level and Str are some things which effect the value of Wc. The value of Wc may range between 25 and -72."],
422     [2, 1, st_ac => "Ac", -120, "Armour Class, how protected you are from being hit by any attack. Lower values are better. Ac is based on your race and is modified by the Dex and current armour worn. For characters that cannot wear armour, Ac improves as their level increases."],
423     [2, 2, st_dam => "Dam", 120, "Damage, how much damage your melee/missile attack inflicts. Higher values indicate a greater amount of damage will be inflicted with each attack."],
424     [2, 3, st_arm => "Arm", 120, "Armour, how much damage (from physical attacks) will be subtracted from successful hits made upon you. This value ranges between 0 to 99%. Current armour worn primarily determines Arm value."],
425     [2, 4, st_spd => "Spd", 10.54, "Speed, how fast you can move. The value of speed may range between nearly 0 (\"very slow\") to higher than 5 (\"lightning fast\"). Base speed is determined from the Dex and modified downward proportionally by the amount of weight carried which exceeds the Max Carry limit. The armour worn also sets the upper limit on speed."],
426     [2, 5, st_wspd => "WSp", 10.54, "Weapon Speed, how many attacks you may make per unit of time (0.120s). Higher values indicate faster attack speed. Current weapon and Dex effect the value of weapon speed."],
427     ) {
428     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
429    
430     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
431 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
432 root 1.180 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
433 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $black, valign => 0, align => -1, text => $label, tooltip => $tooltip);
434 root 1.180 }
435 root 1.155
436 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
437 root 1.155
438 elmex 1.156 my $row = 0;
439     my $col = 0;
440 root 1.155
441 elmex 1.166 my %resist_names = (
442 elmex 1.190 slow => "Slow (slows you down when you are hit by the spell. Monsters will have an opportunity to come near you faster and hit you more often.)",
443     holyw => "Holy Word (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)",
444     conf => "Confusion (If you are hit by confusion you will move into random directions, and likely into monsters.)",
445     fire => "Fire (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
446 root 1.180 depl => "Depletion (some monsters and other effects can cause stats depletion)",
447 elmex 1.190 magic => "Magic (resistance to magic spells like magic missile or similar)",
448 root 1.180 drain => "Draining (some monsters (e.g. vampires) and other effects can steal experience)",
449 elmex 1.190 acid => "Acid (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
450     pois => "Poison (resistance to getting poisoned)",
451     para => "Paralysation (this resistance affects the chance you get paralysed)",
452 root 1.180 deat => "Death (resistance against death spells)",
453 elmex 1.190 phys => "Physical (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
454     blind => "Blind (blind resistance affects the chance of a successful blinding attack)",
455     fear => "Fear (this attack will drive you away from monsters who cast this and hit you successfully, being resistant to this helps a lot when fighting those monsters)",
456 elmex 1.166 tund => "Turn undead",
457 elmex 1.190 elec => "Electricity (resistance againt electricity, spells like large lightning, small lightning, ...)",
458     cold => "Cold (this is your resistance against cold spells like icestorm, snowstorm, ...)",
459 root 1.180 ghit => "Ghost hit (special attack used by ghosts and ghost-like beings)",
460 elmex 1.166 );
461 elmex 1.156 for (qw/slow holyw conf fire depl magic
462     drain acid pois para deat phys
463     blind fear tund elec cold ghit/)
464     {
465 root 1.164 $tbl2->add ($col, $row,
466 elmex 1.156 $STATWIDS->{"res_$_"} =
467 root 1.168 new CFClient::UI::Label
468 root 1.184 font => $FONT_FIXED,
469 root 1.180 template => "-100%",
470     align => +1,
471     valign => 0,
472     can_events => 1,
473     can_hover => 1,
474     tooltip => $resist_names{$_},
475 root 1.168 );
476     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
477 root 1.185 font => $FONT_FIXED,
478 root 1.180 can_hover => 1,
479 root 1.168 can_events => 1,
480 root 1.180 image => "ui/resist/resist_$_.png",
481     tooltip => $resist_names{$_},
482 elmex 1.156 );
483    
484     $row++;
485     if ($row % 6 == 0) {
486     $col += 2;
487     $row = 0;
488     }
489     }
490    
491 elmex 1.157 &set_stats_window_fontsize;
492 elmex 1.156 update_stats_window ({});
493 root 1.155
494 elmex 1.154 $tgw
495     }
496    
497 root 1.169 sub formsep {
498     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
499     }
500    
501 elmex 1.154 sub update_stats_window {
502     my ($stats) = @_;
503    
504 elmex 1.156 # i love text protocols!!!
505 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
506     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
507     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
508     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
509     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
510 elmex 1.156 my $fo_m = 999;
511 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
512     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
513 elmex 1.156
514     $GAUGES->{hp} ->set_value ($hp, $hp_m);
515     $GAUGES->{mana} ->set_value ($sp, $sp_m);
516     $GAUGES->{food} ->set_value ($fo, $fo_m);
517     $GAUGES->{grace} ->set_value ($gr, $gr_m);
518 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
519     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
520     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
521 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
522     $GAUGES->{range} ->set_text ("Rng: " . $rng);
523 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
524 elmex 1.165 $title =~ s/^Player: //;
525     $STATWIDS->{title} ->set_text ("Title: " . $title);
526 elmex 1.156
527 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
528     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
529     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
530     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
531     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
532     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
533     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
534     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
535     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
536     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
537     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
538     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
539     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
540 elmex 1.156
541     my %tbl = (
542     phys => 100,
543     magic => 101,
544     fire => 102,
545     elec => 103,
546     cold => 104,
547     conf => 105,
548     acid => 106,
549     drain => 107,
550     ghit => 108,
551     pois => 109,
552     slow => 110,
553     para => 111,
554     tund => 112,
555     fear => 113,
556 elmex 1.165 depl => 113,
557 elmex 1.156 deat => 115,
558     holyw => 116,
559     blind => 117
560 elmex 1.154 );
561 elmex 1.156
562     for (keys %tbl) {
563     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
564     }
565    
566 elmex 1.154 }
567    
568 root 1.112 sub metaserver_dialog {
569     my $dialog = new CFClient::UI::FancyFrame
570 root 1.150 title => "Metaserver",
571 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
572    
573     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
574    
575     $dialog
576     }
577    
578 root 1.179 my $METASERVER_ATIME;
579    
580 root 1.112 sub update_metaserver {
581 root 1.114 my ($HOST) = @_;
582    
583 root 1.179 return if $METASERVER_ATIME > time;
584     $METASERVER_ATIME = time + 60;
585    
586 root 1.178 my $table = $METASERVER->{table};
587     $table->clear;
588 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
589 root 1.112
590     my $buf;
591    
592     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
593    
594 root 1.178 unless ($fh) {
595     $label->set_text ("unable to contact metaserver: $!");
596     return;
597     }
598    
599 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
600     my $res = sysread $fh, $buf, 8192, length $buf;
601    
602     if (!defined $res) {
603     $_[0]->w->cancel;
604 root 1.178 $label->set_text ("error while retrieving server list: $!");
605 root 1.112 } elsif ($res == 0) {
606     $_[0]->w->cancel;
607     status "server list retrieved";
608 root 1.113
609 root 1.178 utf8::decode $buf if utf8::valid $buf;
610 root 1.113
611     $table->clear;
612    
613 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
614 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
615     for 0 .. $#col;
616    
617     my @align = qw(1 0 1 1 -1);
618    
619     my $y = 0;
620 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
621 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
622    
623     for ($desc) {
624     s/<br>/\n/gi;
625     s/<li>/\n· /gi;
626     s/<.*?>//sgi;
627     s/&/&amp;/g;
628     s/</&lt;/g;
629     s/>/&gt;/g;
630     }
631    
632     $uptime = sprintf "%dd %02d:%02d:%02d",
633     (int $m->[8] / 86400),
634     (int $m->[8] / 3600) % 24,
635     (int $m->[8] / 60) % 60,
636     $m->[8] % 60;
637    
638     $m = [$users, $host, $uptime, $version, $desc];
639    
640     $y++;
641 root 1.114
642     $table->add (0, $y, new CFClient::UI::VBox children => [
643 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
644 root 1.114 $HOST->set_text ($CFG->{host} = $host);
645     }),
646     (new CFClient::UI::Empty expand => 1),
647     ]);
648    
649 root 1.140 $table->add ($_ + 1, $y, new CFClient::UI::Label align => $align[$_], text => $m->[$_], fontsize => 0.8)
650 root 1.113 for 0 .. $#$m;
651     }
652 root 1.112 }
653     });
654     }
655    
656 root 1.111 sub server_setup {
657     my $dialog = new CFClient::UI::FancyFrame
658 root 1.150 title => "Server Setup",
659 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
660 root 1.81
661 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
662 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
663 root 1.112
664     {
665     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
666    
667 elmex 1.166 $vbox->add (
668     my $HOST = new CFClient::UI::Entry
669     expand => 1,
670     text => $CFG->{host},
671     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
672     connect_changed => sub {
673     my ($self, $value) = @_;
674     $CFG->{host} = $value;
675     }
676     );
677 root 1.112
678     $METASERVER = metaserver_dialog;
679 elmex 1.101
680 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
681     expand => 1,
682     text => "Metaserver",
683     other => $METASERVER,
684 root 1.167 tooltip => "Show a list of avaible crossfire servers",
685 elmex 1.166 connect_open => sub {
686     update_metaserver $HOST;
687     }
688     );
689 root 1.112 }
690 root 1.81
691 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
692 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
693     text => $CFG->{user},
694     tooltip => "The name of your character on the server",
695     connect_changed => sub {
696     my ($self, $value) = @_;
697     $CFG->{user} = $value;
698     }
699     );
700 root 1.81
701 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
702 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
703     text => $CFG->{password},
704     hidden => 1,
705     tooltip => "The password for your character",
706     connect_changed => sub {
707     my ($self, $value) = @_;
708     $CFG->{password} = $value;
709     }
710     );
711 elmex 1.101
712 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
713 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
714 root 1.81 req_w => 100,
715     range => [$CFG->{mapsize}, 10, 100 + 1, 1],
716 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
717     ."If you set this to a high value you will be able to see further for example.",
718 root 1.81 connect_changed => sub {
719     my ($self, $value) = @_;
720    
721     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
722     },
723     );
724    
725 elmex 1.101 $table->add (1, 8, new CFClient::UI::Button expand => 1, align => 0, text => "Login", connect_activate => sub {
726 root 1.84 start_game;
727 root 1.82 });
728    
729 root 1.98 $dialog
730 root 1.81 }
731 root 1.58
732 root 1.111 sub message_window {
733 root 1.99 my $window = new CFClient::UI::FancyFrame
734 root 1.150 title => "Messages",
735 root 1.186 border_bg => [1, 1, 1, 1],
736     bg => [0, 0, 0, 0.5],
737 root 1.124 user_w => int $::WIDTH / 3,
738     user_h => int $::HEIGHT / 5,
739 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
740    
741 root 1.105 $vbox->add ($LOGVIEW = new CFClient::UI::TextView
742     expand => 1,
743 root 1.168 font => $FONT_FIXED,
744 root 1.105 fontsize => $::CFG->{log_fontsize},
745     );
746    
747 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
748 elmex 1.118 connect_focus_in => sub {
749     my ($input, $prev_focus) = @_;
750    
751     delete $input->{refocus_map};
752    
753     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
754     $input->{refocus_map} = 1;
755     }
756     delete $input->{auto_activated};
757     },
758 root 1.116 connect_activate => sub {
759 elmex 1.100 my ($input, $text) = @_;
760     $input->set_text ('');
761    
762     if ($text =~ /^\/(.*)/) {
763 root 1.123 $::CONN->user_send ($1);
764 elmex 1.100 } else {
765 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
766 root 1.123 $::CONN->user_send ("$say_cmd $text");
767 elmex 1.100 }
768 elmex 1.118 if ($input->{refocus_map}) {
769     delete $input->{refocus_map};
770     $MAPWIDGET->focus_in
771     }
772 root 1.116 },
773     connect_escape => sub {
774 elmex 1.102 $MAPWIDGET->focus_in
775 root 1.116 },
776     );
777 elmex 1.102
778     $CONSOLE = {
779     window => $window,
780     input => $input
781     };
782 root 1.99
783     $window
784     }
785    
786 elmex 1.191 sub make_inventory_window {
787     my $invwin = new CFClient::UI::FancyFrame user_w => 300, user_h => 300, title => "Inventory";
788     $invwin->add (my $hb = new CFClient::UI::HBox);
789     $hb->add ($INV = new CFClient::UI::Inventory expand => 1);
790     $hb->add (my $rng = new CFClient::UI::Slider vertical => 1);
791     $INV->set_range ($rng);
792     $invwin
793     }
794    
795 root 1.89 sub sdl_init {
796 root 1.145 CFClient::SDL_Init
797 root 1.89 and die "SDL::Init failed!\n";
798     }
799    
800 root 1.134 sub video_init {
801 root 1.89 sdl_init;
802    
803 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
804     $FULLSCREEN = $CFG->{fullscreen};
805 root 1.89 $FAST = $CFG->{fast};
806 root 1.84
807 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
808     or die "SDL_SetVideoMode failed!\n";
809 root 1.2
810 root 1.86 $SDL_ACTIVE = 1;
811    
812 root 1.87 $LAST_REFRESH = time - 0.01;
813 root 1.45
814 root 1.67 CFClient::gl_init;
815 root 1.30
816 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
817 root 1.39
818 root 1.52 #############################################################################
819    
820 root 1.99 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100;
821 root 1.140 $DEBUG_STATUS->show;
822 root 1.52
823 root 1.72 $STATUS_LINE = new CFClient::UI::Label
824 root 1.77 padding => 0,
825 root 1.140 y => $HEIGHT - $FONTSIZE * 1.8;
826     $STATUS_LINE->show;
827 root 1.51
828 root 1.72 $ALT_ENTER_MESSAGE = new CFClient::UI::Label
829 root 1.123 padding => 0,
830 root 1.140 fontsize => 0.8,
831 root 1.123 markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode";
832 root 1.140 $ALT_ENTER_MESSAGE->show;
833     $ALT_ENTER_MESSAGE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h});
834 root 1.30
835 root 1.187 CFClient::UI::FancyFrame->new (
836     border_bg => [1, 1, 1, 192/255],
837     bg => [1, 1, 1, 0],
838 root 1.189 child => ($MAPMAP = new CFClient::MapWidget::MapMap),
839 root 1.187 )->show;
840    
841     $MAPWIDGET = new CFClient::MapWidget;
842 elmex 1.102 $MAPWIDGET->connect (activate_console => sub {
843 elmex 1.103 my ($mapwidget, $preset) = @_;
844    
845 elmex 1.102 if ($CONSOLE) {
846 elmex 1.118 $CONSOLE->{input}->{auto_activated} = 1;
847 elmex 1.102 $CONSOLE->{input}->focus_in;
848 elmex 1.103
849     if ($preset && $CONSOLE->{input}->get_text eq '') {
850     $CONSOLE->{input}->set_text ($preset);
851     }
852 elmex 1.102 }
853     });
854 root 1.187 $MAPWIDGET->show;
855     $MAPWIDGET->focus_in;
856 root 1.81
857 root 1.187 $BUTTONBAR = new CFClient::UI::HBox;
858 root 1.111
859     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup);
860     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup);
861     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window);
862    
863 root 1.192 make_gauge_window->show; # XXX: this has to be set before make_stats_window as make_stats_window calls update_stats_window which updated the gauges also X-D
864    
865 root 1.167 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window);
866 elmex 1.191 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window);
867 root 1.167
868 root 1.111 $BUTTONBAR->add (new CFClient::UI::Button text => "Save Config", connect_activate => sub {
869     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
870     status "Configuration Saved";
871     });
872 root 1.98
873 root 1.187 $BUTTONBAR->show;
874    
875 root 1.119 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
876 root 1.2 }
877    
878 root 1.134 sub video_shutdown {
879 root 1.111 $CFClient::UI::ROOT->{children} = [];
880 root 1.177 undef $CFClient::UI::GRAB;
881     undef $CFClient::UI::HOVER;
882 root 1.86 undef $SDL_ACTIVE;
883 root 1.134 }
884    
885 root 1.153 my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
886 root 1.135 my $bgmusic;#TODO#hack#d#
887    
888 root 1.153 sub audio_music_finished {
889     return unless $CFG->{bgm_enable};
890    
891     # TODO: hack, do play loop and mood music
892     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
893     $bgmusic->play (0);
894    
895     push @bgmusic, shift @bgmusic;
896     }
897    
898 root 1.134 sub audio_init {
899 root 1.139 if ($CFG->{audio_enable}) {
900 root 1.134 if (open my $fh, "<:utf8", CFClient::find_rcfile "sounds/config") {
901 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
902     CFClient::Mix_AllocateChannels 8;
903 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
904 root 1.134
905 root 1.153 audio_music_finished;
906 root 1.135
907 root 1.134 while (<$fh>) {
908     next if /^\s*#/;
909     next if /^\s*$/;
910    
911     my ($file, $volume, $event) = split /\s+/, $_, 3;
912    
913     push @SOUNDS, "$volume,$file";
914    
915     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
916 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
917 root 1.134 $chunk->volume ($volume * 128 / 100);
918     $chunk
919     };
920     }
921     } else {
922     status "unable to open sound config: $!";
923     }
924     }
925     }
926    
927     sub audio_shutdown {
928 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
929 root 1.134 undef $SDL_MIXER;
930     @SOUNDS = ();
931     %AUDIO_CHUNKS = ();
932 root 1.62 }
933    
934 root 1.87 my %animate_object;
935     my $animate_timer;
936    
937     my $want_refresh;
938     my $can_refresh;
939    
940     my $fps = 9;
941    
942 root 1.30 sub force_refresh {
943 root 1.87 $fps = $fps * 0.95 + 1 / ($NOW - $LAST_REFRESH) * 0.05;
944     debug sprintf "%3.2f", $fps;
945    
946 root 1.96 $want_refresh = 0;
947 root 1.87 $can_refresh = 0;
948    
949 root 1.111 $CFClient::UI::ROOT->draw;
950 root 1.1
951 root 1.148 CFClient::SDL_GL_SwapBuffers;
952 root 1.87
953     $LAST_REFRESH = $NOW;
954 root 1.1 }
955    
956 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
957     $NOW = time;
958    
959 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
960     for CFClient::SDL_PollEvent;
961 root 1.87
962     if (%animate_object) {
963     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
964     $want_refresh++;
965     }
966    
967     if ($want_refresh) {
968     force_refresh;
969     } else {
970     $can_refresh = 1;
971     }
972     });
973 root 1.64
974 root 1.30 sub refresh {
975 root 1.87 $want_refresh++;
976 root 1.30 }
977    
978 root 1.45 sub animation_start {
979     my ($widget) = @_;
980 root 1.87 $animate_object{$widget} = $widget;
981 root 1.45 }
982    
983     sub animation_stop {
984     my ($widget) = @_;
985 root 1.87 delete $animate_object{$widget};
986 root 1.45 }
987    
988 root 1.2 @conn::ISA = Crossfire::Protocol::;
989 root 1.1
990 elmex 1.125 sub conn::stats_update {
991     my ($self, $stats) = @_;
992    
993 elmex 1.154 update_stats_window ($stats);
994 elmex 1.125 }
995    
996 root 1.89 sub conn::user_send {
997 root 1.88 my ($self, $command) = @_;
998    
999 root 1.123 $self->send_command ($command);
1000 root 1.88 status $command;
1001     }
1002    
1003 root 1.119 sub conn::map_scroll {
1004     my ($self, $dx, $dy) = @_;
1005    
1006     $MAP->scroll ($dx, $dy);
1007     }
1008    
1009 root 1.94 sub conn::feed_map1a {
1010     my ($self, $data) = @_;
1011    
1012 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
1013 root 1.1
1014 root 1.95 $MAP->map1a_update ($data);
1015 root 1.69 $MAPWIDGET->update;
1016 root 1.1 }
1017    
1018 root 1.116 sub conn::flush_map {
1019     my ($self) = @_;
1020    
1021     my $map_info = delete $self->{map_info}
1022     or return;
1023    
1024     my ($hash, $x, $y, $w, $h) = @$map_info;
1025    
1026     my $data = $MAP->get_rect ($x, $y, $w, $h);
1027     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1028 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1029 root 1.116 }
1030 root 1.1
1031 root 1.2 sub conn::map_clear {
1032 root 1.1 my ($self) = @_;
1033    
1034 root 1.116 $self->flush_map;
1035 root 1.150 delete $self->{neigh_map};
1036 root 1.116
1037 root 1.95 $MAP->clear;
1038 root 1.1 }
1039    
1040 root 1.116
1041 root 1.119 sub conn::load_map($$$) {
1042     my ($self, $hash, $x, $y) = @_;
1043 root 1.115
1044 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1045     $data = Compress::LZF::decompress $data;
1046 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1047 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1048     my $data = $TILECACHE->get ($id)
1049     or next;
1050    
1051     $self->set_texture ($id => $data);
1052     }
1053     }
1054 root 1.115 }
1055    
1056 root 1.152 # this method does a "flood fill" into every tile direction
1057     # it assumes that tiles are arranged in a rectangular grid,
1058     # i.e. a map is the same as the left of the right map etc.
1059     # failure to comply are harmless and result in display errors
1060     # at worst.
1061 root 1.119 sub conn::flood_fill {
1062 root 1.150 my ($self, $gx, $gy, $path, $hash, $flags) = @_;
1063 root 1.119
1064 root 1.121 # the server does not allow map paths > 6
1065 root 1.187 return if 7 <= length $path;
1066 root 1.120
1067 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1068    
1069     for (
1070     [1, 0, -1],
1071     [2, 1, 0],
1072     [3, 0, 1],
1073     [4, -1, 0],
1074     ) {
1075     my ($tile, $dx, $dy) = @$_;
1076    
1077     my $gx = $gx + $dx;
1078     my $gy = $gy + $dy;
1079    
1080 root 1.119 next unless $flags & (1 << ($tile - 1));
1081 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1082 root 1.119
1083 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1084     if (my $info = $neigh->[$tile]) {
1085     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1086 root 1.119
1087 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1088     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1089    
1090     } else {
1091     $self->send_mapinfo ("spatial $path$tile", sub {
1092     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1093 root 1.119
1094 root 1.150 return if $mode ne "spatial";
1095 root 1.119
1096 root 1.150 $x += $MAP->ox;
1097     $y += $MAP->oy;
1098    
1099     $self->load_map ($hash, $x, $y)
1100     unless $self->{neigh_map}{$hash}[5]++;#d#
1101 root 1.119
1102 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1103 root 1.119
1104 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1105     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1106     });
1107     }
1108 root 1.119 }
1109     }
1110    
1111     sub conn::map_change {
1112     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1113    
1114     $self->flush_map;
1115    
1116     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1117    
1118 root 1.187 my $mapmapw = $MAPMAP->{w};
1119     my $mapmaph = $MAPMAP->{h};
1120 root 1.150
1121     $self->{neigh_rect} = [
1122 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1123     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1124 root 1.150 ];
1125 root 1.119
1126 root 1.150 delete $self->{neigh_grid};
1127     $self->flood_fill (0, 0, "", $hash, $flags);
1128 root 1.119
1129     $x += $ox;
1130     $y += $oy;
1131    
1132     $self->{map_info} = [$hash, $x, $y, $w, $h];
1133    
1134 elmex 1.158 my $map = $self->{map_info}[0];
1135     $map =~ s/^.*?\/([^\/]+)$/\1/;
1136     $STATWIDS->{map}->set_text ("Map: " . $map);
1137 elmex 1.157
1138 root 1.119 $self->load_map ($hash, $x, $y);
1139     }
1140    
1141 root 1.19 sub conn::face_find {
1142 root 1.116 my ($self, $facenum, $face) = @_;
1143    
1144     my $hash = "$face->{chksum},$face->{name}";
1145    
1146     my $id = $FACEMAP->get ($hash);
1147    
1148     unless ($id) {
1149     # create new id for face
1150     # i love transactions
1151     for (1..100) {
1152     my $txn = $CFClient::DB_ENV->txn_begin;
1153     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1154     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1155     $id++;
1156     if ($FACEMAP->put (id => $id) == 0
1157     && $FACEMAP->put ($hash => $id) == 0) {
1158     $txn->txn_commit;
1159    
1160     goto gotid;
1161     }
1162     }
1163     $txn->abort;
1164     }
1165 root 1.19
1166 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1167     }
1168 root 1.114
1169 root 1.116 gotid:
1170     $face->{id} = $id;
1171     $MAP->set_face ($facenum => $id);
1172 root 1.173 $self->{faceid}[$facenum] = $id;#d#
1173 root 1.116 $TILECACHE->get ($id)
1174 root 1.19 }
1175    
1176 root 1.2 sub conn::face_update {
1177 root 1.95 my ($self, $facenum, $face) = @_;
1178 root 1.19
1179 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1180    
1181     $self->set_texture ($face->{id} => delete $face->{image});
1182     }
1183 root 1.1
1184 root 1.116 sub conn::set_texture {
1185     my ($self, $id, $data) = @_;
1186 root 1.95
1187 root 1.116 $self->{texture}[$id] ||= do {
1188     my $tex =
1189     new_from_image CFClient::Texture
1190 root 1.173 $data, minify => 1, mipmap => 1;
1191 root 1.116
1192     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1193     $MAPWIDGET->update;
1194    
1195     $tex
1196     };
1197 root 1.1 }
1198    
1199 root 1.134 sub conn::sound_play {
1200     my ($self, $x, $y, $soundnum, $type) = @_;
1201    
1202 root 1.139 $SDL_MIXER
1203     or return;
1204    
1205 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1206     or return;
1207    
1208 root 1.146 $chunk->play;
1209 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1210 root 1.134 }
1211    
1212 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1213    
1214 root 1.33 sub conn::query {
1215     my ($self, $flags, $prompt) = @_;
1216    
1217 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1218     $LAST_QUERY = $prompt;
1219    
1220     my $dialog = new CFClient::UI::FancyFrame
1221     title => "Query",
1222     child => my $vbox = new CFClient::UI::VBox;
1223    
1224     $vbox->add (new CFClient::UI::Label
1225     max_w => $::WIDTH * 0.4,
1226     text => $prompt);
1227    
1228     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1229     $vbox->add (my $hbox = new CFClient::HBox);
1230     $hbox->add (new CFClient::Button
1231     text => "No",
1232     connect_activate => sub {
1233     $self->send ("reply n");
1234     $dialog->destroy;
1235     $MAPWIDGET->focus_in;
1236     }
1237     );
1238     $hbox->add (new CFClient::Button
1239     text => "Yes",
1240     connect_activate => sub {
1241     $self->send ("reply y");
1242     $dialog->destroy;
1243     $MAPWIDGET->focus_in;
1244     },
1245     );
1246    
1247     $dialog->focus_in;
1248    
1249     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1250     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1251     $vbox->add (my $entry = new CFClient::UI::Entry
1252     connect_changed => sub {
1253     $self->send ("reply $_[1]");
1254     $dialog->destroy;
1255     $MAPWIDGET->focus_in;
1256     },
1257     );
1258    
1259     $entry->focus_in;
1260    
1261     } else {
1262     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1263    
1264     $vbox->add (my $entry = new CFClient::UI::Entry
1265     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1266     connect_activate => sub {
1267     $self->send ("reply $_[1]");
1268     $dialog->destroy;
1269     $MAPWIDGET->focus_in;
1270     },
1271     );
1272    
1273     $entry->focus_in;
1274     }
1275    
1276     $dialog->show;
1277 root 1.33 }
1278    
1279 root 1.99 sub conn::drawinfo {
1280     my ($self, $color, $text) = @_;
1281    
1282     my @color = (
1283     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1284     [1.00, 1.00, 1.00],
1285 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1286 root 1.99 [1.00, 0.00, 0.00],
1287     [1.00, 0.54, 0.00],
1288     [0.11, 0.56, 1.00],
1289     [0.93, 0.46, 0.00],
1290     [0.18, 0.54, 0.34],
1291     [0.56, 0.73, 0.56],
1292     [0.80, 0.80, 0.80],
1293     [0.55, 0.41, 0.13],
1294     [0.99, 0.77, 0.26],
1295     [0.74, 0.65, 0.41],
1296     );
1297    
1298     $LOGVIEW->add_paragraph ($color[$color], $text);
1299     }
1300    
1301 root 1.144 sub conn::spell_add {
1302 root 1.143 my ($self, $spell) = @_;
1303    
1304 root 1.171 # TODO
1305     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1306     $MAPWIDGET->add_command ("invoke $spell->{name}", $spell->{message});
1307     $MAPWIDGET->add_command ("cast $spell->{name}", $spell->{message});
1308 root 1.144 }
1309    
1310     sub conn::spell_delete {
1311     my ($self, $spell) = @_;
1312     }
1313    
1314     sub conn::addme_success {
1315     my ($self) = @_;
1316    
1317     for my $skill (values %{$self->{skill_info}}) {
1318 root 1.171 $MAPWIDGET->add_command ("ready_skill $skill", "Ready the skill '$skill'");
1319     $MAPWIDGET->add_command ("use_skill $skill", "Immediately use the skill '$skill'");
1320 root 1.144 }
1321 root 1.143 }
1322    
1323 root 1.173 sub update_floorbox {
1324     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1325     $FLOORBOX->clear;
1326     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1327    
1328     my @items = values %{ $CONN->{container}{0} };
1329    
1330     # we basically have to use the same sorting as everybody else
1331     @items = sort { $a->{type} <=> $b->{type} } @items;
1332    
1333     for my $item (reverse @items) {
1334     my $desc = $item->{nrof} < 2
1335     ? $item->{name}
1336     : "$item->{nrof} $item->{name_pl}";
1337     # todo: animation widget, face widget, weight(?) etc.
1338     $FLOORBOX->add (my $hbox = new CFClient::UI::HBox
1339     tooltip => (CFClient::UI::Label->escape ($desc)
1340     . "\n<small>leftclick - pick up\nmiddle click - apply\nrightclick - menu</small>"),
1341     can_hover => 1,
1342     can_events => 1,
1343 root 1.174 connect_button_down => sub {
1344     my ($self, $ev, $x, $y) = @_;
1345    
1346     # todo: maybe put examine on 1? but should just be a tooltip :(
1347     if ($ev->{button} == 1) {
1348     $CONN->send ("move $CONN->{player}{tag} $item->{tag} 0");
1349     } elsif ($ev->{button} == 2) {
1350     $CONN->send ("apply $item->{tag}");
1351     } elsif ($ev->{button} == 3) {
1352 root 1.189 CFClient::UI::Menu->new (
1353     items => [
1354     ["examine", sub { $CONN->send ("examine $item->{tag}") }],
1355     [
1356     $item->{flags} & Crossfire::Protocol::F_LOCKED ? "lock" : "unlock",
1357     sub { $CONN->send ("lock $item->{tag}") },
1358     ],
1359     ["mark", sub { $CONN->send ("mark $item->{tag}") }],
1360     ["apply", sub { $CONN->send ("apply $item->{tag}") }],
1361     ],
1362     )->popup ($ev);
1363 root 1.174 }
1364    
1365     1
1366     },
1367 root 1.173 );
1368    
1369     $hbox->add (new CFClient::UI::Face
1370 root 1.175 can_events => 0,
1371     face => $item->{face},
1372     anim => $item->{anim},
1373     animspeed => $item->{animspeed},
1374 root 1.173 );
1375    
1376     $hbox->add (new CFClient::UI::Label
1377 root 1.175 can_events => 0,
1378     text => $desc,
1379 root 1.173 );
1380     }
1381     });
1382     refresh;
1383     }
1384    
1385 root 1.169 sub conn::container_add {
1386     my ($self, $id, $items) = @_;
1387    
1388 root 1.173 update_floorbox if $id == 0;
1389 elmex 1.191 if ($self->{player}{tag} == $id) {
1390     $INV->set_items ($self->{container}{$self->{player}{tag}});
1391     }
1392 root 1.169 # $self-<{player}{tag} => player inv
1393     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1394     }
1395    
1396     sub conn::container_clear {
1397     my ($self, $id) = @_;
1398 root 1.173
1399     update_floorbox if $id == 0;
1400 elmex 1.191 if ($self->{player}{tag} == $id) {
1401     $INV->set_items ($self->{container}{$id});
1402     }
1403    
1404 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1405     }
1406    
1407 root 1.173 sub conn::item_delete {
1408     my ($self, @items) = @_;
1409    
1410     for (@items) {
1411     update_floorbox if $_->{container} == 0;
1412 elmex 1.191 if ($self->{player}{tag} == $_->{container}) {
1413     $INV->set_items ($self->{container}{$_->{container}});
1414     }
1415 root 1.173 }
1416     }
1417    
1418     sub conn::item_update {
1419     my ($self, $item) = @_;
1420    
1421     update_floorbox if $item->{container} == 0;
1422 elmex 1.191 if ($self->{player}{tag} == $item->{container}) {
1423     $INV->set_items ($self->{container}{$item->{container}});
1424     }
1425 root 1.173 }
1426    
1427 root 1.87 %SDL_CB = (
1428 root 1.145 CFClient::SDL_QUIT => sub {
1429 root 1.87 Event::unloop -1;
1430     },
1431 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1432 root 1.87 },
1433 root 1.153 CFClient::SDL_VIDEOEXPOSE => \&refresh,
1434     CFClient::SDL_ACTIVEEVENT => sub {
1435     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1436 root 1.87 },
1437 root 1.145 CFClient::SDL_KEYDOWN => sub {
1438 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1439 root 1.87 # alt-enter
1440 root 1.134 video_shutdown;
1441 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1442 root 1.134 video_init;
1443 root 1.87 } else {
1444 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1445 elmex 1.23 }
1446 root 1.87 },
1447 root 1.153 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1448     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1449     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1450     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1451     CFClient::SDL_USEREVENT => \&audio_music_finished,
1452 root 1.87 );
1453 elmex 1.23
1454 root 1.1 #############################################################################
1455    
1456 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1457    
1458 root 1.116 $TILECACHE = CFClient::db_table "tilecache";
1459     $FACEMAP = CFClient::db_table "facemap";
1460 root 1.114
1461 root 1.67 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1462 elmex 1.10
1463 root 1.90 my %DEF_CFG = (
1464 root 1.105 sdl_mode => 0,
1465 root 1.90 width => 640,
1466     height => 480,
1467 root 1.105 fullscreen => 0,
1468 root 1.90 fast => 0,
1469 root 1.169 map_scale => 0.5,
1470 root 1.97 fow_enable => 1,
1471 root 1.90 fow_intensity => 0.45,
1472 root 1.92 fow_smooth => 0,
1473 root 1.140 gui_fontsize => 1,
1474 elmex 1.157 log_fontsize => 1,
1475 elmex 1.158 gauge_fontsize => 1,
1476     gauge_size => 0.35,
1477 elmex 1.157 stat_fontsize => 1,
1478 root 1.90 mapsize => 100,
1479     host => "crossfire.schmorp.de",
1480 elmex 1.101 say_command => 'say',
1481 root 1.139 audio_enable => 1,
1482     bgm_enable => 1,
1483 root 1.149 bgm_volume => 0.25,
1484 root 1.90 );
1485    
1486     while (my ($k, $v) = each %DEF_CFG) {
1487     $CFG->{$k} = $v unless exists $CFG->{$k};
1488     }
1489 elmex 1.12
1490 root 1.89 sdl_init;
1491 root 1.87
1492 root 1.93 @SDL_MODES = reverse
1493     grep $_->[0] >= 640 && $_->[1] >= 480,
1494 root 1.145 CFClient::SDL_ListModes;
1495 root 1.87
1496 root 1.89 @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1497    
1498     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1499    
1500 root 1.65 {
1501 root 1.168 my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1502     DejaVuSans.ttf
1503     DejaVuSansMono.ttf
1504     DejaVuSans-Bold.ttf
1505     DejaVuSansMono-Bold.ttf
1506     DejaVuSans-Oblique.ttf
1507     DejaVuSansMono-Oblique.ttf
1508     DejaVuSans-BoldOblique.ttf
1509     DejaVuSansMono-BoldOblique.ttf
1510     );
1511 root 1.65
1512 root 1.67 CFClient::add_font $_ for @fonts;
1513 root 1.168
1514     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1515     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1516    
1517     $FONT_PROP->make_default;
1518 root 1.65 }
1519 root 1.40
1520 root 1.134 video_init;
1521     audio_init;
1522 root 1.122
1523 root 1.87 Event::loop;
1524 root 1.19
1525 root 1.148 END { CFClient::SDL_Quit }
1526 root 1.131
1527 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1528    
1529     Pclient is a Crossfire+ and Crossfire game client.
1530    
1531     =head2 Features
1532    
1533     =over 4
1534    
1535     =item Fullscreen Map
1536    
1537     PClient can uses a fullscreen map, which greatly enhances how much of the
1538     game world you can see.
1539    
1540     =item Persistent Map Cache (Crossfire+ only)
1541    
1542     PClient can persistently cache all map data it received from the
1543     server. This not only allows it to display an overview map, but also
1544     ensures that once-explored areas will be available the next time you want
1545     to explore more.
1546    
1547     =item Hardware acceleration
1548    
1549     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1550     acceleration. Most modern graphics cards have difficulties with 2D
1551     acceleration, while 3D graphics is accelerated well.
1552    
1553     =item No arbitrary limits
1554    
1555     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1556     limits (like a fixed amount of face numbers). There are still limits, but
1557     they are not arbitrarily low :)
1558    
1559     =back
1560    
1561 root 1.179 =head1 USAGE
1562    
1563     =head2 The Map
1564    
1565     The map is always displayed in the background, behind all other windows and UI elements.
1566    
1567     #TODO# middle-click scrolls
1568     #
1569     # keys:
1570     #
1571     # a apply
1572     # keypad moves, kp_5 applies ranged attack to self
1573    
1574     Starting to type enters the I<completion mode>. In that mode, you can type
1575     abbreviations or commands and have them executed as soon as they match a
1576     valid command. This is best explained by a few examples:
1577    
1578     Typing B<climb> will display a list of commands with I<climb> in their
1579     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1580    
1581     You can abbreviate commands by typing only the first character of every
1582     word. For example, typing I<iwor> will likely select I<invoke word of
1583     recall>, while I<ccfo> will select I<cast create food>. Likewise, I<rscli>
1584     will likely select I<ready_skill climbing> and I<usl> will give you
1585     I<use_skill levitation>.
1586    
1587     =head2 The map overview
1588    
1589     #TODO#
1590    
1591     =head2 The Status area in the lower right corner
1592    
1593     #TODO#
1594    
1595     =head2 The I<Statistics>/I>Stats> window
1596    
1597     #TODO#
1598    
1599 root 1.178 =head1 FAQ
1600    
1601     =over 4
1602    
1603     =item The client is very sluggish and slow, what can I do about this?
1604    
1605     Most likely, you don't have accelerated OpenGL support. Try to find a
1606     newer driver, or a driver from your hardware vendor, that features OpenGL
1607     support.
1608    
1609     If this is not an option, the following Setup options reduce the load and
1610     will likely make the client playable with sofwtare rendering (it will
1611     still be slow, though):
1612    
1613     =over 4
1614    
1615     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1616    
1617     =item Enable B<Fast & Ugly> mode
1618    
1619     =item Disable B<Fog of War>
1620    
1621     =item Increase B<Map Scale>
1622    
1623     =back
1624    
1625     =back
1626    
1627     =head1 AUTHOR
1628    
1629     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1630    
1631    
1632 root 1.82