ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.21
Committed: Sun May 28 01:16:04 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.20: +20 -20 lines
Log Message:
better overall layout, homogenous boxes

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     use strict;
4     use utf8;
5    
6     # do things only needed for single-binary version (par)
7     BEGIN {
8     if (%PAR::LibCache) {
9     @INC = grep ref, @INC; # weed out all paths except pars loader refs
10    
11     while (my ($filename, $zip) = each %PAR::LibCache) {
12     for ($zip->memberNames) {
13     next unless /^\/root\/(.*)/;
14     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
15     unless -e "$ENV{PAR_TEMP}/$1";
16     }
17     }
18    
19     # TODO: pango-rc file, anybody?
20    
21     unshift @INC, $ENV{PAR_TEMP};
22     }
23     }
24    
25     # need to do it again because that pile of garbage called PAR nukes it before main
26     unshift @INC, $ENV{PAR_TEMP}
27     if %PAR::LibCache;
28    
29     use Time::HiRes 'time';
30     use Pod::POM;
31     use Event;
32    
33     use Crossfire;
34 root 1.12 use Crossfire::Protocol::Constants;
35 root 1.1
36     use Compress::LZF;
37    
38     use CFClient;
39 root 1.10 use CFClient::OpenGL ();
40 root 1.11 use CFClient::Protocol;
41 root 1.1 use CFClient::UI;
42     use CFClient::MapWidget;
43    
44     $Event::DIED = sub {
45     # TODO: display dialog box or so
46     CFClient::error $_[1];
47     };
48    
49     #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
50    
51     our $VERSION = '0.1';
52    
53     my $MAX_FPS = 60;
54     my $MIN_FPS = 5; # unused as of yet
55    
56     our $META_SERVER = "crossfire.real-time.com:13326";
57    
58     our $LAST_REFRESH;
59     our $NOW;
60    
61     our $CFG;
62     our $CONN;
63     our $FAST; # fast, low-quality mode, possibly useful for software-rendering
64    
65     our $WANT_REFRESH;
66     our $CAN_REFRESH;
67    
68     our @SDL_MODES;
69     our $WIDTH;
70     our $HEIGHT;
71     our $FULLSCREEN;
72     our $FONTSIZE;
73    
74     our $FONT_PROP;
75     our $FONT_FIXED;
76    
77     our $MAP;
78     our $MAPMAP;
79     our $MAPWIDGET;
80     our $BUTTONBAR;
81     our $LOGVIEW;
82     our $CONSOLE;
83     our $METASERVER;
84     our $LOGIN_BUTTON;
85     our $QUIT_DIALOG;
86    
87     our $FLOORBOX;
88     our $GAUGES;
89     our $STATWIDS;
90    
91     our $SDL_ACTIVE;
92     our %SDL_CB;
93    
94     our $SDL_MIXER;
95     our @SOUNDS; # event => file mapping
96     our %AUDIO_CHUNKS; # audio files
97    
98     our $ALT_ENTER_MESSAGE;
99     our $STATUSBOX;
100     our $DEBUG_STATUS;
101    
102     our $INV;
103     our $INVR;
104     our $INVR_LBL;
105    
106     sub status {
107     $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
108     }
109    
110     sub debug {
111     $DEBUG_STATUS->set_text ($_[0]);
112     my ($w, $h) = $DEBUG_STATUS->size_request;
113     $DEBUG_STATUS->move ($WIDTH - $w, 0);
114     }
115    
116     sub start_game {
117     status "logging in...";
118    
119     my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
120    
121 root 1.11 my ($host, $port) = split /:/, $CFG->{host};
122    
123 root 1.1 $MAP = new CFClient::Map $mapsize, $mapsize;
124    
125     $CONN = eval {
126 root 1.11 new CFClient::Protocol
127 root 1.1 host => $host,
128     port => $port || 13327,
129     user => $CFG->{user},
130     pass => $CFG->{password},
131     mapw => $mapsize,
132     maph => $mapsize,
133 root 1.11
134     map_widget => $MAPWIDGET,
135     logview => $LOGVIEW,
136     statusbox => $STATUSBOX,
137     map => $MAP,
138     mapmap => $MAPMAP,
139    
140     sound_play => sub {
141     my ($x, $y, $soundnum, $type) = @_;
142    
143     $SDL_MIXER
144     or return;
145    
146     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
147     or return;
148    
149     $chunk->play;
150     },
151 root 1.1 };
152    
153     if ($CONN) {
154     CFClient::lowdelay fileno $CONN->{fh};
155    
156     $LOGIN_BUTTON->set_text ("Logout");
157     status "login successful";
158    
159     $BUTTONBAR->{children}[1]->emit ("activate")
160     if $BUTTONBAR->{children}[1]->{state};
161    
162     } else {
163     status "unable to connect";
164     stop_game();
165     }
166     }
167    
168     sub stop_game {
169     return unless $CONN;
170    
171     status "connection closed";
172     $LOGIN_BUTTON->set_text ("Login");
173     $CONN->destroy;
174     $CONN = 0; # false, does not autovivify
175    
176     $BUTTONBAR->{children}[1]->emit ("activate")
177     unless $BUTTONBAR->{children}[1]->{state};
178     }
179    
180     sub client_setup {
181     my $dialog = new CFClient::UI::FancyFrame
182 elmex 1.19 req_x => 1,
183     req_y => $HEIGHT * (1/8),
184 elmex 1.16 name => "client_setup",
185 root 1.1 title => "Client Setup",
186     child => (my $vbox = new CFClient::UI::VBox);
187     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
188    
189     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
190     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
191    
192     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1]);
193     $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
194    
195     $mode_slider->connect (changed => sub {
196     my ($self, $value) = @_;
197    
198     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
199     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
200     });
201     $mode_slider->emit (changed => $mode_slider->{range}[0]);
202    
203     my $row = 1;
204    
205     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
206     $table->add (1, $row++, new CFClient::UI::CheckBox
207     state => $CFG->{fullscreen},
208     tooltip => "Bring the client into fullscreen mode.",
209 root 1.18 on_changed => sub {
210 root 1.1 my ($self, $value) = @_;
211     $CFG->{fullscreen} = $value;
212     }
213     );
214    
215     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
216     $table->add (1, $row++, new CFClient::UI::CheckBox
217     state => $CFG->{fast},
218     tooltip => "Lower the visual quality considerably to speed up rendering.",
219 root 1.18 on_changed => sub {
220 root 1.1 my ($self, $value) = @_;
221     $CFG->{fast} = $value;
222     }
223     );
224    
225     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
226     $table->add (1, $row++, new CFClient::UI::Slider
227     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
228     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
229 root 1.18 on_changed => sub {
230 root 1.1 my ($self, $value) = @_;
231     $CFG->{map_scale} = 2 ** $value;
232     }
233     );
234    
235     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
236     $table->add (1, $row++, new CFClient::UI::CheckBox
237     state => $CFG->{fow_enable},
238     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
239 root 1.18 on_changed => sub {
240 root 1.1 my ($self, $value) = @_;
241     $CFG->{fow_enable} = $value;
242     }
243     );
244    
245     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
246     $table->add (1, $row++, new CFClient::UI::Slider
247     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
248     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
249 root 1.18 on_changed => sub {
250 root 1.1 my ($self, $value) = @_;
251     $CFG->{fow_intensity} = $value;
252     }
253     );
254    
255     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
256     $table->add (1, $row++, new CFClient::UI::CheckBox
257     state => $CFG->{fow_smooth},
258     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
259 root 1.18 on_changed => sub {
260 root 1.1 my ($self, $value) = @_;
261     $CFG->{fow_smooth} = $value;
262 root 1.15 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::OpenGL::GL_VERSION < 1.2;
263 root 1.1 }
264     );
265    
266     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
267     $table->add (1, $row++, new CFClient::UI::Slider
268     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
269     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
270 root 1.18 on_changed => sub { $CFG->{gui_fontsize} = $_[1] },
271 root 1.1 );
272    
273     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
274     $table->add (1, $row++, new CFClient::UI::Slider
275     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
276     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
277 root 1.18 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
278 root 1.1 );
279    
280     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
281    
282     $table->add (1, $row++, new CFClient::UI::Slider
283     range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 0.1],
284     tooltip => "The font size used by the <b>statistics window</b> only. Changes are instant.",
285 root 1.18 on_changed => sub {
286 root 1.1 $CFG->{stat_fontsize} = $_[1];
287     &set_stats_window_fontsize;
288     }
289     );
290    
291     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
292     $table->add (1, $row++, new CFClient::UI::Slider
293     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
294     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
295 root 1.18 on_changed => sub {
296 root 1.1 $CFG->{gauge_fontsize} = $_[1];
297     &set_gauge_window_fontsize;
298     }
299     );
300    
301     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
302     $table->add (1, $row++, new CFClient::UI::Slider
303 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
304     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
305     on_changed => sub {
306 root 1.1 $CFG->{gauge_size} = $_[1];
307     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
308     }
309     );
310    
311     $table->add (1, $row++, new CFClient::UI::Button
312     expand => 1, align => 0, text => "Apply",
313     tooltip => "Apply the video settings",
314 root 1.18 on_activate => sub {
315 root 1.1 video_shutdown ();
316     video_init ();
317     }
318     );
319    
320     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
321     $table->add (1, $row++, new CFClient::UI::CheckBox
322     state => $CFG->{audio_enable},
323     tooltip => "<b>Master Audio Enable.</b> If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
324 root 1.18 on_changed => sub {
325 root 1.1 $CFG->{audio_enable} = $_[1];
326     }
327     );
328     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
329 root 1.18 # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
330 root 1.1 # $CFG->{effects_volume} = $_[1];
331     # });
332     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
333     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
334     $hbox->add (new CFClient::UI::CheckBox
335     expand => 1, state => $CFG->{bgm_enable},
336     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
337 root 1.18 on_changed => sub {
338 root 1.1 $CFG->{bgm_enable} = $_[1];
339     }
340     );
341     $hbox->add (new CFClient::UI::Slider
342     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
343     tooltip => "The volume of the background music. Changes are instant.",
344 root 1.18 on_changed => sub {
345 root 1.1 $CFG->{bgm_volume} = $_[1];
346     CFClient::MixMusic::volume $_[1] * 128;
347     }
348     );
349    
350     $table->add (1, $row++, new CFClient::UI::Button
351     expand => 1, align => 0, text => "Apply",
352     tooltip => "Apply the audio settings",
353 root 1.18 on_activate => sub {
354 root 1.1 audio_shutdown ();
355     audio_init ();
356     }
357     );
358    
359     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
360     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
361     text => $CFG->{say_command},
362     tooltip => "This is the command that will be used if you write a line in the message window entry or press <b>\"</b> in the map window. "
363     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
364     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
365 root 1.18 on_changed => sub {
366 root 1.1 my ($self, $value) = @_;
367     $CFG->{say_command} = $value;
368     }
369     );
370    
371     $dialog
372     }
373    
374     sub set_stats_window_fontsize {
375     for (values %{$STATWIDS}) {
376     $_->set_fontsize ($::CFG->{stat_fontsize});
377     }
378     }
379    
380     sub set_gauge_window_fontsize {
381     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
382     $_->set_fontsize ($::CFG->{gauge_fontsize});
383     }
384     }
385    
386     sub make_gauge_window {
387     my $gh = int $HEIGHT * $CFG->{gauge_size};
388    
389     my $win = new CFClient::UI::Frame (
390 root 1.20 req_x => 0,
391     req_y => -1,
392     def_w => $WIDTH,
393     def_h => $gh,
394 root 1.1 );
395    
396     $win->add (my $hbox = new CFClient::UI::HBox
397     children => [
398     (new CFClient::UI::HBox expand => 1),
399     (new CFClient::UI::VBox children => [
400     (new CFClient::UI::Empty expand => 1),
401 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
402 root 1.1 ]),
403     (my $vbox = new CFClient::UI::VBox),
404     ],
405     );
406    
407     $vbox->add (new CFClient::UI::HBox
408     expand => 1,
409     children => [
410     (new CFClient::UI::Empty expand => 1),
411     (my $hb = new CFClient::UI::HBox),
412     ],
413     );
414    
415     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
416     tooltip => "<b>Health points</b>. 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.");
417     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
418     tooltip => "<b>Spell points</b>. 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.");
419     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
420     tooltip => "<b>Grace points</b> - 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.");
421     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
422     tooltip => "<b>Food</b>. 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.");
423    
424     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
425     tooltip => "<b>Experience points and overall level</b> - 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.");
426     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
427     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
428    
429     $GAUGES = {
430     exp => $exp, win => $win, range => $rng,
431     food => $fg, mana => $mg, hp => $hg, grace => $gg
432     };
433    
434     &set_gauge_window_fontsize;
435    
436     $win
437     }
438    
439     sub make_stats_window {
440 elmex 1.19 my $tgw = new CFClient::UI::FancyFrame
441     req_y => $HEIGHT * (2/8),
442     req_x => -1,
443     title => "Stats",
444     name => "stats_window";
445 root 1.1
446     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
447     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
448     can_hover => 1, can_events => 1,
449     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
450     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
451     can_hover => 1, can_events => 1,
452     tooltip => "The map you are currently on (if supported by the server).");
453    
454 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
455     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
456     can_hover => 1, can_events => 1,
457 root 1.15 tooltip => "The weight of the player including all inventory items.");
458 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
459     can_hover => 1, can_events => 1,
460 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
461 elmex 1.5
462    
463 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
464     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
465    
466     my $color2 = [1, 1, 0];
467    
468     for (
469     [0, 0, st_str => "Str", 30, "<b>Physical Strength</b>, determines damage dealt with weapons, how much you can carry, and how often you can attack"],
470     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
471     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
472     [0, 3, st_int => "Int", 30, "<b>Intelligence</b>, your ability to learn and use skills and incantations (both prayers and magic) and determines how much spell points you can have"],
473     [0, 4, st_wis => "Wis", 30, "<b>Wisdom</b>, the ability to learn and use divine magic (prayers). Determines how many grace points you can have"],
474     [0, 5, st_pow => "Pow", 30, "<b>Power</b>, your magical potential. Influences the strength of spell effects, and also how much your spell and grace points increase when leveling up"],
475     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
476    
477     [2, 0, st_wc => "Wc", -120, "<b>Weapon Class</b>, 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."],
478     [2, 1, st_ac => "Ac", -120, "<b>Armour Class</b>, 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."],
479     [2, 2, st_dam => "Dam", 120, "<b>Damage</b>, how much damage your melee/missile attack inflicts. Higher values indicate a greater amount of damage will be inflicted with each attack."],
480     [2, 3, st_arm => "Arm", 120, "<b>Armour</b>, 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."],
481     [2, 4, st_spd => "Spd", 10.54, "<b>Speed</b>, 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."],
482     [2, 5, st_wspd => "WSp", 10.54, "<b>Weapon Speed</b>, 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."],
483     ) {
484     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
485    
486     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
487     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
488     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
489     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
490     }
491    
492     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
493    
494     my $row = 0;
495     my $col = 0;
496    
497     my %resist_names = (
498     slow => "<b>Slow</b> (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.)",
499     holyw => "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)",
500     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
501     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
502     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
503     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
504     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
505     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
506     pois => "<b>Poison</b> (resistance to getting poisoned)",
507     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
508     deat => "<b>Death</b> (resistance against death spells)",
509     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
510     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
511     fear => "<b>Fear</b> (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)",
512     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
513     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
514     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
515     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
516     );
517     for (qw/slow holyw conf fire depl magic
518     drain acid pois para deat phys
519     blind fear tund elec cold ghit/)
520     {
521     $tbl2->add ($col, $row,
522     $STATWIDS->{"res_$_"} =
523     new CFClient::UI::Label
524     font => $FONT_FIXED,
525     template => "-100%",
526     align => +1,
527     valign => 0,
528     can_events => 1,
529     can_hover => 1,
530     tooltip => $resist_names{$_},
531     );
532     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
533     font => $FONT_FIXED,
534     can_hover => 1,
535     can_events => 1,
536     image => "ui/resist/resist_$_.png",
537     tooltip => $resist_names{$_},
538     );
539    
540     $row++;
541     if ($row % 6 == 0) {
542     $col += 2;
543     $row = 0;
544     }
545     }
546    
547     &set_stats_window_fontsize;
548     update_stats_window ({});
549    
550     $tgw
551     }
552    
553     sub formsep {
554     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
555     }
556    
557     sub update_stats_window {
558     my ($stats) = @_;
559    
560 root 1.12 # I love text protocols...
561    
562     my $hp = $stats->{+CS_STAT_HP} * 1;
563     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
564     my $sp = $stats->{+CS_STAT_SP} * 1;
565     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
566     my $fo = $stats->{+CS_STAT_FOOD} * 1;
567 root 1.1 my $fo_m = 999;
568 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
569     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
570 root 1.1
571     $GAUGES->{hp} ->set_value ($hp, $hp_m);
572     $GAUGES->{mana} ->set_value ($sp, $sp_m);
573     $GAUGES->{food} ->set_value ($fo, $fo_m);
574     $GAUGES->{grace} ->set_value ($gr, $gr_m);
575 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
576     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
577     my $rng = $stats->{+CS_STAT_RANGE};
578 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
579     $GAUGES->{range} ->set_text ("Rng: " . $rng);
580 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
581 root 1.1 $title =~ s/^Player: //;
582     $STATWIDS->{title} ->set_text ("Title: " . $title);
583    
584 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
585     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
586     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
587     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
588     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
589     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
590     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
591     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
592     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
593     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
594     $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_ARMOUR});
595     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
596     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
597 root 1.1
598 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
599 elmex 1.5
600 root 1.12 # TODO: replace by CS_STAT_RES_xxx constants
601 root 1.1 my %tbl = (
602     phys => 100,
603     magic => 101,
604     fire => 102,
605     elec => 103,
606     cold => 104,
607     conf => 105,
608     acid => 106,
609     drain => 107,
610     ghit => 108,
611     pois => 109,
612     slow => 110,
613     para => 111,
614     tund => 112,
615     fear => 113,
616     depl => 113,
617     deat => 115,
618     holyw => 116,
619 root 1.12 blind => 117,
620 root 1.1 );
621    
622 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
623     for keys %tbl;
624 root 1.1 }
625    
626     sub metaserver_dialog {
627     my $dialog = new CFClient::UI::FancyFrame
628     title => "Server List",
629     child => (my $vbox = new CFClient::UI::VBox);
630    
631     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
632    
633     $dialog
634     }
635    
636     my $METASERVER_ATIME;
637    
638     sub update_metaserver {
639     my ($HOST) = @_;
640    
641     return if $METASERVER_ATIME > time;
642     $METASERVER_ATIME = time + 60;
643    
644     my $table = $METASERVER->{table};
645     $table->clear;
646     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
647    
648     my $buf;
649    
650     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
651    
652     unless ($fh) {
653     $label->set_text ("unable to contact metaserver: $!");
654     return;
655     }
656    
657     Event->io (fd => $fh, poll => 'r', cb => sub {
658     my $res = sysread $fh, $buf, 8192, length $buf;
659    
660     if (!defined $res) {
661     $_[0]->w->cancel;
662     $label->set_text ("error while retrieving server list: $!");
663     } elsif ($res == 0) {
664     $_[0]->w->cancel;
665     status "server list retrieved";
666    
667     utf8::decode $buf if utf8::valid $buf;
668    
669     $table->clear;
670    
671     my @col = qw(Use #Users Host Uptime Version Description);
672     $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
673     for 0 .. $#col;
674    
675     my @align = qw(1 0 1 1 -1);
676    
677     my $y = 0;
678     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
679     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
680    
681     for ($desc) {
682     s/<br>/\n/gi;
683     s/<li>/\n· /gi;
684     s/<.*?>//sgi;
685     s/&/&amp;/g;
686     s/</&lt;/g;
687     s/>/&gt;/g;
688     }
689    
690     $uptime = sprintf "%dd %02d:%02d:%02d",
691     (int $m->[8] / 86400),
692     (int $m->[8] / 3600) % 24,
693     (int $m->[8] / 60) % 60,
694     $m->[8] % 60;
695    
696     $m = [$users, $host, $uptime, $version, $desc];
697    
698     $y++;
699    
700     $table->add (0, $y, new CFClient::UI::VBox children => [
701 root 1.18 (new CFClient::UI::Button text => "Use", on_activate => sub {
702 root 1.1 $HOST->set_text ($CFG->{host} = $host);
703     }),
704     (new CFClient::UI::Empty expand => 1),
705     ]);
706    
707     $table->add ($_ + 1, $y, new CFClient::UI::Label
708     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
709     for 0 .. $#$m;
710     }
711     }
712     });
713     }
714    
715     sub server_setup {
716     my $dialog = new CFClient::UI::FancyFrame
717 root 1.21 x => $WIDTH * (1/3),
718 elmex 1.19 y => $HEIGHT * (1/8),
719     name => "server_setup",
720     title => "Server Setup",
721     child => (my $vbox = new CFClient::UI::VBox),
722     on_visibility_change => sub {
723 root 1.21 my ($self, $visible) = @_;
724     $self->center if $visible;
725     },
726     ;
727 elmex 1.19
728 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
729     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
730    
731     {
732     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
733    
734     $vbox->add (
735     my $HOST = new CFClient::UI::Entry
736     expand => 1,
737     text => $CFG->{host},
738     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
739 root 1.18 on_changed => sub {
740 root 1.1 my ($self, $value) = @_;
741     $CFG->{host} = $value;
742     }
743     );
744    
745     $METASERVER = metaserver_dialog;
746    
747     $vbox->add (new CFClient::UI::Flopper
748     expand => 1,
749     text => "Server List",
750     other => $METASERVER,
751     tooltip => "Show a list of available crossfire servers",
752 root 1.18 on_open => sub {
753 root 1.1 update_metaserver $HOST;
754     }
755     );
756     }
757    
758     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
759     $table->add (1, 4, new CFClient::UI::Entry
760     text => $CFG->{user},
761     tooltip => "The name of your character on the server",
762 root 1.18 on_changed => sub {
763 root 1.1 my ($self, $value) = @_;
764     $CFG->{user} = $value;
765     }
766     );
767    
768     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
769     $table->add (1, 5, new CFClient::UI::Entry
770     text => $CFG->{password},
771     hidden => 1,
772     tooltip => "The password for your character",
773 root 1.18 on_changed => sub {
774 root 1.1 my ($self, $value) = @_;
775     $CFG->{password} = $value;
776     }
777     );
778    
779     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
780     $table->add (1, 7, new CFClient::UI::Slider
781     req_w => 100,
782     range => [$CFG->{mapsize}, 10, 100, 0, 1],
783     tooltip => "This is the size of the portion of the map update the server sends you. "
784     . "If you set this to a high value you will be able to see further, "
785     . "but you also increase bandwidth requirements and latency. "
786     . "This option is only used once at log-in.",
787 root 1.18 on_changed => sub {
788 root 1.1 my ($self, $value) = @_;
789    
790     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
791     },
792     );
793    
794     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
795     $table->add (1, 8, new CFClient::UI::CheckBox
796     state => $CFG->{face_prefetch},
797     tooltip => "<b>Background Image Prefetch</b>\n\n"
798     . "If enabled, the client automatically pre-fetches images from the server. "
799     . "This might increase or create lag, but increases the chances "
800     . "of faces being ready for display when you encounter them. "
801     . "It also uses up server bandwidth on every connect, "
802     . "so only set it if you really need to prefetch images. "
803     . "This option can be set and unset any time.",
804 root 1.18 on_changed => sub { $CFG->{face_prefetch} = $_[1] },
805 root 1.1 );
806    
807     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
808     $table->add (1, 9, new CFClient::UI::Entry
809     text => $CFG->{output_count},
810     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
811 root 1.18 on_changed => sub { $CFG->{output_count} = $_[1] },
812 root 1.1 );
813    
814     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
815     $table->add (1, 10, new CFClient::UI::Entry
816     text => $CFG->{output_sync},
817     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
818 root 1.18 on_changed => sub { $CFG->{output_sync} = $_[1] },
819 root 1.1 );
820    
821     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
822     expand => 1,
823     align => 0,
824     text => "Login",
825 root 1.18 on_activate => sub {
826 root 1.1 $CONN ? stop_game
827     : start_game;
828     },
829     );
830    
831     $dialog
832     }
833    
834     sub message_window {
835     my $window = new CFClient::UI::FancyFrame
836 elmex 1.16 name => "message_window",
837 root 1.1 title => "Messages",
838     border_bg => [1, 1, 1, 1],
839     bg => [0, 0, 0, 0.75],
840 elmex 1.19 req_x => -1,
841 root 1.20 req_y => 0,
842     def_w => int $::WIDTH / 3,
843     def_h => int $::HEIGHT / 5,
844 root 1.1 child => (my $vbox = new CFClient::UI::VBox);
845    
846     $vbox->add ($LOGVIEW);
847    
848     $vbox->add (my $input = new CFClient::UI::Entry
849     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
850     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
851     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
852     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
853 root 1.18 on_focus_in => sub {
854 root 1.1 my ($input, $prev_focus) = @_;
855    
856     delete $input->{refocus_map};
857    
858     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
859     $input->{refocus_map} = 1;
860     }
861     delete $input->{auto_activated};
862     },
863 root 1.18 on_activate => sub {
864 root 1.1 my ($input, $text) = @_;
865     $input->set_text ('');
866    
867     if ($text =~ /^\/(.*)/) {
868     $::CONN->user_send ($1);
869     } else {
870     my $say_cmd = $::CFG->{say_command} || 'say';
871     $::CONN->user_send ("$say_cmd $text");
872     }
873     if ($input->{refocus_map}) {
874     delete $input->{refocus_map};
875     $MAPWIDGET->focus_in
876     }
877     },
878 root 1.18 on_escape => sub {
879 root 1.1 $MAPWIDGET->focus_in
880     },
881     );
882    
883     $CONSOLE = {
884     window => $window,
885     input => $input
886     };
887    
888     $window
889     }
890    
891     sub open_quit_dialog {
892     unless ($QUIT_DIALOG) {
893     $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
894    
895     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
896    
897     $vb->add (new CFClient::UI::Label
898     text => "You should find a savebed and apply it first!",
899     max_w => $WIDTH * 0.25,
900     ellipsize => 0,
901     );
902     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
903     $hb->add (new CFClient::UI::Button
904     text => "Ok",
905     expand => 1,
906 root 1.18 on_activate => sub { $QUIT_DIALOG->hide },
907 root 1.1 );
908     $hb->add (new CFClient::UI::Button
909     text => "Quit anyway",
910     expand => 1,
911 root 1.18 on_activate => sub { exit },
912 root 1.1 );
913 root 1.21 }
914 root 1.1
915 root 1.21 $QUIT_DIALOG->show;
916     $QUIT_DIALOG->center;
917 root 1.1 }
918    
919     sub make_inventory_window {
920     my $invwin = new CFClient::UI::FancyFrame
921 root 1.21 def_w => $WIDTH * 7/8,
922     def_h => $HEIGHT * 7/8,
923 elmex 1.19 title => "Inventory",
924 root 1.20 name => "inventory_window",
925 elmex 1.19 on_visibility_change => sub {
926 root 1.21 my ($self, $visible) = @_;
927     $self->center if $visible;
928     },
929     ;
930 root 1.1
931 root 1.21 $invwin->add (my $hb = new CFClient::UI::HBox homogeneous => 1);
932 root 1.1
933 root 1.21 $hb->add (my $vb1 = new CFClient::UI::VBox);
934     $vb1->add (new CFClient::UI::Label align => 0, text => "Player");
935     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
936 root 1.1
937 root 1.21 $hb->add (my $vb2 = new CFClient::UI::VBox);
938 elmex 1.17
939 elmex 1.14 $vb2->add (my $hb2 = new CFClient::UI::HBox);
940 root 1.21 $hb2->add ($INVR_LBL = new CFClient::UI::Label align => 0, expand => 1, text => "Floor");
941 elmex 1.14 $hb2->add (new CFClient::UI::Button
942     text => "Close",
943     tooltip => "Close the currently open container (if one is open)",
944 root 1.18 on_activate => sub {
945 elmex 1.14 $CONN->send ("apply $CONN->{open_container}")
946     if $CONN->{open_container} != 0;
947     },
948     );
949    
950 root 1.1 $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
951    
952     $invwin
953     }
954    
955     sub make_help_window {
956     my $win = new CFClient::UI::FancyFrame
957 root 1.20 def_w => int $WIDTH * 7/8,
958     def_h => int $HEIGHT * 7/8,
959     title => "Documentation";
960 root 1.1
961     $win->add (my $vbox = new CFClient::UI::VBox);
962    
963     $vbox->add (my $buttons = new CFClient::UI::HBox);
964     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
965    
966     for (
967     [intro => "Introduction"],
968     [manual => "Manual"],
969     [command_help => "Commands"],
970     [skill_help => "Skills"],
971     ) {
972     my ($pod, $label) = @$_;
973    
974     $buttons->add (new CFClient::UI::Button
975     text => $label,
976 root 1.18 on_activate => sub {
977 root 1.1 my $parser = new Pod::POM;
978     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
979    
980     $viewer->clear;
981    
982     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
983     for @{ CFClient::pod_to_pango_list $pom };
984    
985     $viewer->set_offset (0);
986     },
987     );
988     }
989    
990     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
991    
992     $win
993     }
994    
995     sub sdl_init {
996     CFClient::SDL_Init
997     and die "SDL::Init failed!\n";
998     }
999    
1000     sub video_init {
1001     sdl_init;
1002    
1003     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1004    
1005     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1006    
1007     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1008     $FULLSCREEN = $CFG->{fullscreen};
1009     $FAST = $CFG->{fast};
1010    
1011     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1012     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1013    
1014     $SDL_ACTIVE = 1;
1015     $LAST_REFRESH = time - 0.01;
1016    
1017 root 1.10 CFClient::OpenGL::init;
1018 root 1.1
1019     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1020    
1021     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1022    
1023     #############################################################################
1024    
1025     if ($DEBUG_STATUS) {
1026     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1027     } else {
1028     # create the widgets
1029    
1030     $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
1031     $DEBUG_STATUS->show;
1032    
1033     $STATUSBOX = new CFClient::UI::Statusbox;
1034     $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
1035    
1036     (new CFClient::UI::Frame
1037     bg => [0, 0, 0, 0.4],
1038     req_y => -1,
1039     child => $STATUSBOX,
1040     )->show;
1041    
1042     CFClient::UI::FancyFrame->new (
1043     border_bg => [1, 1, 1, 192/255],
1044     bg => [1, 1, 1, 0],
1045     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1046     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1047     ),
1048     )->show;
1049    
1050     $MAPWIDGET = new CFClient::MapWidget;
1051     $MAPWIDGET->connect (activate_console => sub {
1052     my ($mapwidget, $preset) = @_;
1053    
1054     if ($CONSOLE) {
1055     $CONSOLE->{input}->{auto_activated} = 1;
1056     $CONSOLE->{input}->focus_in;
1057    
1058     if ($preset && $CONSOLE->{input}->get_text eq '') {
1059     $CONSOLE->{input}->set_text ($preset);
1060     }
1061     }
1062     });
1063     $MAPWIDGET->show;
1064     $MAPWIDGET->focus_in;
1065    
1066     $LOGVIEW = new CFClient::UI::TextView
1067     expand => 1,
1068     font => $FONT_FIXED,
1069     fontsize => $::CFG->{log_fontsize},
1070     can_hover => 1,
1071     can_events => 1,
1072     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1073     ;
1074    
1075     $BUTTONBAR = new CFClient::UI::HBox;
1076    
1077     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1078     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1079     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1080     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1081     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1082     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1083    
1084     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
1085    
1086     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1087     tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1088     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1089     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1090    
1091     $BUTTONBAR->add (new CFClient::UI::Button
1092     text => "Save Config",
1093     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1094 root 1.18 on_activate => sub {
1095 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1096 root 1.1 CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1097     status "Configuration Saved";
1098     },
1099     );
1100    
1101     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1102     tooltip => "View Documentation");
1103    
1104     $BUTTONBAR->add (new CFClient::UI::Button
1105 root 1.18 text => "Quit",
1106     tooltip => "Terminates the program",
1107     on_activate => sub {
1108 root 1.1 if ($CONN) {
1109     open_quit_dialog;
1110     } else {
1111     exit;
1112     }
1113     },
1114     );
1115    
1116     $BUTTONBAR->show;
1117    
1118     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1119    
1120     # delay till geometry is constant
1121     $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1122     $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1123     my $widget = $GAUGES->{win};
1124     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1125     });
1126     force_refresh ();
1127     }
1128     }
1129    
1130     sub video_shutdown {
1131     undef $SDL_ACTIVE;
1132     }
1133    
1134     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1135     my $bgmusic;#TODO#hack#d#
1136    
1137     sub audio_channel_finished {
1138     my ($channel) = @_;
1139    
1140     #warn "channel $channel finished\n";#d#
1141     }
1142    
1143     sub audio_music_finished {
1144     return unless $CFG->{bgm_enable};
1145    
1146     # TODO: hack, do play loop and mood music
1147     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1148     $bgmusic->play (0);
1149    
1150     push @bgmusic, shift @bgmusic;
1151     }
1152    
1153     sub audio_init {
1154     if ($CFG->{audio_enable}) {
1155     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1156     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1157    
1158     unless ($SDL_MIXER) {
1159     status "Unable to open sound device: there will be no sound";
1160     return;
1161     }
1162    
1163     CFClient::Mix_AllocateChannels 8;
1164     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1165    
1166     audio_music_finished;
1167    
1168     while (<$fh>) {
1169     next if /^\s*#/;
1170     next if /^\s*$/;
1171    
1172     my ($file, $volume, $event) = split /\s+/, $_, 3;
1173    
1174     push @SOUNDS, "$volume,$file";
1175    
1176     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1177     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1178     $chunk->volume ($volume * 128 / 100);
1179     $chunk
1180     };
1181     }
1182     } else {
1183     status "unable to open sound config: $!";
1184     }
1185     }
1186     }
1187    
1188     sub audio_shutdown {
1189     CFClient::Mix_CloseAudio if $SDL_MIXER;
1190     undef $SDL_MIXER;
1191     @SOUNDS = ();
1192     %AUDIO_CHUNKS = ();
1193     }
1194    
1195     my %animate_object;
1196     my $animate_timer;
1197    
1198     my $fps = 9;
1199    
1200     my %demo;#d#
1201    
1202     sub force_refresh {
1203     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1204     debug sprintf "%3.2f", $fps;
1205    
1206     $CFClient::UI::ROOT->draw;
1207    
1208     $WANT_REFRESH = 0;
1209     $CAN_REFRESH = 0;
1210     $LAST_REFRESH = $NOW;
1211    
1212     0 && do {
1213     # some weird model-drawing code, just a joke right now
1214     use CFClient::OpenGL;
1215    
1216     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1217     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1218     $demo{r} ||= do {
1219     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1220     $mod->{v} = pack "f*", @{$mod->{v}};
1221     $_ = [scalar @$_, pack "S!*", @$_]
1222     for values %{$mod->{g}};
1223     $mod
1224     };
1225    
1226     my $r = $demo{r} or die;
1227    
1228     glDepthMask 1;
1229     glClear GL_DEPTH_BUFFER_BIT;
1230     glEnable GL_TEXTURE_2D;
1231     glEnable GL_DEPTH_TEST;
1232     glEnable GL_CULL_FACE;
1233     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1234    
1235     glMatrixMode GL_PROJECTION;
1236     glLoadIdentity;
1237     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1238     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1239     glMatrixMode GL_MODELVIEW;
1240     glLoadIdentity;
1241    
1242     glPushMatrix;
1243     glTranslate 0, 0, -800;
1244     glScale 1, -1, 1;
1245     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1246     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1247     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1248     glScale 50, 50, 50;
1249    
1250     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1251     while (my ($k, $v) = each %{$r->{g}}) {
1252     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1253     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1254     }
1255    
1256     glPopMatrix;
1257    
1258     glShadeModel GL_FLAT;
1259     glDisable GL_DEPTH_TEST;
1260     glDisable GL_TEXTURE_2D;
1261     glDepthMask 0;
1262    
1263     $WANT_REFRESH++;
1264     };
1265    
1266     CFClient::SDL_GL_SwapBuffers;
1267     }
1268    
1269     my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1270     $NOW = time;
1271    
1272     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1273     for CFClient::SDL_PollEvent;
1274    
1275     if (%animate_object) {
1276     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1277     $WANT_REFRESH++;
1278     }
1279    
1280     if ($WANT_REFRESH) {
1281     force_refresh;
1282     } else {
1283     $CAN_REFRESH = 1;
1284     }
1285     });
1286    
1287     sub animation_start {
1288     my ($widget) = @_;
1289     $animate_object{$widget} = $widget;
1290     }
1291    
1292     sub animation_stop {
1293     my ($widget) = @_;
1294     delete $animate_object{$widget};
1295     }
1296    
1297     # check once/second for faces that need to be prefetched
1298     # this should, of course, only run on demand, but
1299     # SDL forces worse things on us....
1300    
1301     Event->timer (after => 1, interval => 0.25, cb => sub {
1302     $CONN->face_prefetch
1303     if $CONN;
1304     });
1305    
1306     %SDL_CB = (
1307     CFClient::SDL_QUIT => sub {
1308     Event::unloop -1;
1309     },
1310     CFClient::SDL_VIDEORESIZE => sub {
1311     },
1312     CFClient::SDL_VIDEOEXPOSE => sub {
1313     CFClient::UI::full_refresh;
1314     },
1315     CFClient::SDL_ACTIVEEVENT => sub {
1316     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1317     },
1318     CFClient::SDL_KEYDOWN => sub {
1319     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1320     # alt-enter
1321     video_shutdown;
1322     $CFG->{fullscreen} = !$CFG->{fullscreen};
1323     video_init;
1324     } else {
1325     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1326     }
1327     },
1328     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1329     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1330     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1331     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1332     CFClient::SDL_USEREVENT => sub {
1333     if ($_[0]{code} == 1) {
1334     audio_channel_finished $_[0]{data1};
1335     } elsif ($_[0]{code} == 0) {
1336     audio_music_finished;
1337     }
1338     },
1339     );
1340    
1341     #############################################################################
1342    
1343     $SIG{INT} = $SIG{TERM} = sub { exit };
1344    
1345     {
1346     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1347    
1348     CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1349 elmex 1.16 CFClient::UI::set_layout ($::CFG->{layout});
1350 root 1.1
1351     my %DEF_CFG = (
1352     sdl_mode => 0,
1353     width => 640,
1354     height => 480,
1355     fullscreen => 0,
1356     fast => 0,
1357     map_scale => 1,
1358     fow_enable => 1,
1359     fow_intensity => 0.45,
1360     fow_smooth => 0,
1361     gui_fontsize => 1,
1362     log_fontsize => 1,
1363     gauge_fontsize=> 1,
1364     gauge_size => 0.35,
1365     stat_fontsize => 1,
1366     mapsize => 100,
1367     host => "crossfire.schmorp.de",
1368     say_command => 'say',
1369     audio_enable => 1,
1370     bgm_enable => 1,
1371     bgm_volume => 0.25,
1372     face_prefetch => 0,
1373     output_sync => 1,
1374     output_count => 1,
1375     );
1376    
1377     while (my ($k, $v) = each %DEF_CFG) {
1378     $CFG->{$k} = $v unless exists $CFG->{$k};
1379     }
1380    
1381     sdl_init;
1382    
1383     @SDL_MODES = reverse
1384     grep $_->[0] >= 640 && $_->[1] >= 480,
1385     CFClient::SDL_ListModes;
1386    
1387     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1388    
1389     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1390    
1391     {
1392     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1393     DejaVuSans.ttf
1394     DejaVuSansMono.ttf
1395     DejaVuSans-Bold.ttf
1396     DejaVuSansMono-Bold.ttf
1397     DejaVuSans-Oblique.ttf
1398     DejaVuSansMono-Oblique.ttf
1399     DejaVuSans-BoldOblique.ttf
1400     DejaVuSansMono-BoldOblique.ttf
1401     );
1402    
1403     CFClient::add_font $_ for @fonts;
1404    
1405     CFClient::pango_init;
1406    
1407     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1408     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1409    
1410     $FONT_PROP->make_default;
1411     }
1412    
1413     # compare mono (ft) vs. rgba (cairo)
1414     # ft - 1.8s, cairo 3s, even in alpha-only mode
1415     # for my $rgba (0..1) {
1416     # my $t1 = Time::HiRes::time;
1417     # for (1..1000) {
1418     # my $layout = CFClient::Layout->new ($rgba);
1419     # $layout->set_text ("hallo" x 100);
1420     # $layout->render;
1421     # }
1422     # my $t2 = Time::HiRes::time;
1423     # warn $t2-$t1;
1424     # }
1425    
1426     video_init;
1427     audio_init;
1428     }
1429    
1430     Event::loop;
1431    
1432     END { CFClient::SDL_Quit }
1433    
1434     =head1 NAME
1435    
1436     pclient - A Crossfire+ and Crossfire game client
1437    
1438     =head1 SYNOPSIS
1439    
1440     Just run it - no commandline arguments are supported.
1441    
1442     =head1 USAGE
1443    
1444     Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
1445     fullscreen and interactively.
1446    
1447     =head1 AUTHOR
1448    
1449     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1450    
1451    
1452