ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.67
Committed: Sun Jun 11 18:36:15 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.66: +10 -0 lines
Log Message:
added primitive server info field

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 Event;
31    
32     use Crossfire;
33 root 1.12 use Crossfire::Protocol::Constants;
34 root 1.1
35     use Compress::LZF;
36    
37     use CFClient;
38 root 1.10 use CFClient::OpenGL ();
39 root 1.11 use CFClient::Protocol;
40 root 1.1 use CFClient::UI;
41     use CFClient::MapWidget;
42    
43 root 1.59 $SIG{QUIT} = sub { Carp::cluck "QUIT" };
44    
45 root 1.1 $Event::DIED = sub {
46     # TODO: display dialog box or so
47 root 1.49 Carp::confess $_[1];#d#TODO: remove when stable
48 root 1.1 CFClient::error $_[1];
49     };
50    
51     #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
52    
53     our $VERSION = '0.1';
54    
55     my $MAX_FPS = 60;
56     my $MIN_FPS = 5; # unused as of yet
57    
58     our $META_SERVER = "crossfire.real-time.com:13326";
59    
60     our $LAST_REFRESH;
61     our $NOW;
62    
63     our $CFG;
64     our $CONN;
65     our $FAST; # fast, low-quality mode, possibly useful for software-rendering
66    
67     our $WANT_REFRESH;
68     our $CAN_REFRESH;
69    
70     our @SDL_MODES;
71     our $WIDTH;
72     our $HEIGHT;
73     our $FULLSCREEN;
74     our $FONTSIZE;
75    
76     our $FONT_PROP;
77     our $FONT_FIXED;
78    
79     our $MAP;
80     our $MAPMAP;
81     our $MAPWIDGET;
82     our $BUTTONBAR;
83     our $LOGVIEW;
84     our $CONSOLE;
85     our $METASERVER;
86     our $LOGIN_BUTTON;
87     our $QUIT_DIALOG;
88 root 1.40 our $HOST_ENTRY;
89 root 1.67 our $SERVER_INFO;
90 root 1.49
91     our $SETUP_DIALOG;
92     our $SETUP_NOTEBOOK;
93     our $SETUP_SERVER;
94     our $SETUP_KEYBOARD;
95     our $SETUP_SPELLS;
96 root 1.1
97 root 1.60 our $STATS_WINDOW;
98     our $MESSAGE_WINDOW;
99 root 1.1 our $FLOORBOX;
100     our $GAUGES;
101     our $STATWIDS;
102    
103     our $SDL_ACTIVE;
104     our %SDL_CB;
105    
106     our $SDL_MIXER;
107     our @SOUNDS; # event => file mapping
108     our %AUDIO_CHUNKS; # audio files
109    
110     our $ALT_ENTER_MESSAGE;
111     our $STATUSBOX;
112     our $DEBUG_STATUS;
113    
114 root 1.23 our $INV_WINDOW;
115 root 1.1 our $INV;
116     our $INVR;
117 elmex 1.27 our $INV_RIGHT_HB;
118 root 1.1
119 elmex 1.34 our $BIND_EDITOR;
120 elmex 1.24
121 elmex 1.43 our $PICKUP_CFG;
122 elmex 1.38
123 root 1.1 sub status {
124     $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
125     }
126    
127     sub debug {
128     $DEBUG_STATUS->set_text ($_[0]);
129     }
130    
131 root 1.60 sub destroy_query_dialog {
132     (delete $_[0]{query_dialog})->destroy
133     if $_[0]{query_dialog};
134     }
135    
136     # server query dialog
137     sub server_query {
138     my ($conn, $flags, $prompt) = @_;
139    
140     $conn->{query_dialog} = my $dialog = new CFClient::UI::FancyFrame
141     x => "center",
142     y => "center",
143     title => "Server Query",
144     child => my $vbox = new CFClient::UI::VBox,
145     ;
146    
147     my @dialog = my $label = new CFClient::UI::Label
148     max_w => $::WIDTH * 0.4,
149     ellipsise => 0,
150     text => $prompt;
151    
152     if ($flags & CS_QUERY_YESNO) {
153     push @dialog, my $hbox = new CFClient::UI::HBox;
154    
155     $hbox->add (new CFClient::UI::Button
156     text => "No",
157     on_activate => sub {
158     $conn->send ("reply n");
159     $dialog->destroy;
160     }
161     );
162     $hbox->add (new CFClient::UI::Button
163     text => "Yes",
164     on_activate => sub {
165     $conn->send ("reply y");
166     destroy_query_dialog $conn;
167     },
168     );
169    
170     $dialog->focus_in;
171    
172     } elsif ($flags & CS_QUERY_SINGLECHAR) {
173     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
174    
175     if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
176     $MESSAGE_WINDOW->show;
177    
178     unshift @dialog, new CFClient::UI::Label
179     max_w => $::WIDTH * 0.4,
180     ellipsise => 0,
181     markup => "\nOr use your keyboard:\n";
182    
183     unshift @dialog, my $table = new CFClient::UI::Table;
184    
185     $table->add (0, 0, new CFClient::UI::Button
186     text => "Next Race",
187     on_activate => sub {
188     $conn->send ("reply n");
189     destroy_query_dialog $conn;
190     },
191     );
192     $table->add (2, 0, new CFClient::UI::Button
193     text => "Accept",
194     on_activate => sub {
195     $conn->send ("reply d");
196     destroy_query_dialog $conn;
197     },
198     );
199    
200     unshift @dialog, new CFClient::UI::Label
201     max_w => $::WIDTH * 0.4,
202     ellipsise => 0,
203     markup =>
204     "<big><b>Character Creation: Race</b></big>\n\n"
205     . "Look at the <b>Messages</b> window to see a description of this race "
206     . "(<small>or hover with your mouse over the bottommost entry in the status area in the lower left area of the screen</small>) "
207     . "and the center of the screen to see how this race looks like "
208     . "(<small>this is below this dialog window, you may need to click on the display area to make it visible</small>).\n\n"
209     . "You can look at another race, or accept this race (you will come back to this race eventually, "
210     . "so you can take your time making this important choice."
211     ;
212    
213     } elsif ($prompt =~ /roll new stats/) {
214     if (my $stat = delete $conn->{stat_change_with}) {
215     $conn->send ("reply $stat");
216     destroy_query_dialog $conn;
217     return;
218     }
219    
220     $STATS_WINDOW->show;
221     $MESSAGE_WINDOW->hide;
222    
223     unshift @dialog, new CFClient::UI::Label
224     max_w => $::WIDTH * 0.4,
225     ellipsise => 0,
226     markup => "\nOr use your keyboard:\n";
227    
228     unshift @dialog, my $table = new CFClient::UI::Table;
229    
230     # left: re-roll
231     $table->add (0, 0, new CFClient::UI::Button
232     text => "Roll Again",
233     on_activate => sub {
234     $conn->send ("reply y");
235     destroy_query_dialog $conn;
236     },
237     );
238    
239     # center: swap stats
240     my ($sw1, $sw2) = map +(new CFClient::UI::Combobox
241     value => $_,
242     options => [
243 root 1.64 [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
244     [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
245     [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
246     [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
247     [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
248     [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
249     [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
250 root 1.60 ],
251     ), 1 .. 2;
252    
253     $table->add (2, 0, new CFClient::UI::Button
254     text => "Swap Stats",
255     on_activate => sub {
256     $conn->{stat_change_with} = $sw2->{value};
257     $conn->send ("reply $sw1->{value}");
258     destroy_query_dialog $conn;
259     },
260     );
261     $table->add (2, 1, new CFClient::UI::HBox children => [$sw1, $sw2]);
262    
263     # right: accept
264     $table->add (4, 0, new CFClient::UI::Button
265     text => "Accept",
266     on_activate => sub {
267     $conn->send ("reply n");
268     $STATS_WINDOW->hide;
269     destroy_query_dialog $conn;
270     },
271     );
272    
273     unshift @dialog, new CFClient::UI::Label
274     max_w => $::WIDTH * 0.4,
275     ellipsise => 0,
276     markup =>
277     "<big><b>Character Creation: Stats</b></big>\n\n"
278     . "Look at the <b>Stats</b> window to see your basic stats "
279     . "(first column: 1 strength, 2 dexterity, 3 constitution, 4 intelligence, 5 wisdom, 6 power and 7 charisma).\n\n"
280     . "You can create another set of stats, swap two stat values with each other or accept the stats as they are now and continue. "
281     . "Race selection will influence those values later on."
282     ;
283     }
284    
285     push @dialog, my $entry = new CFClient::UI::Entry
286     on_changed => sub {
287     $conn->send ("reply $_[1]");
288     destroy_query_dialog $conn;
289     },
290     ;
291    
292     $entry->focus_in;
293    
294     } else {
295     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
296    
297     push @dialog, my $entry = new CFClient::UI::Entry
298     $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
299     on_activate => sub {
300     $conn->send ("reply $_[1]");
301     destroy_query_dialog $conn;
302     },
303     ;
304    
305     $entry->focus_in;
306     }
307    
308     $vbox->add (@dialog);
309     $dialog->show;
310     }
311    
312 root 1.1 sub start_game {
313     status "logging in...";
314    
315 root 1.23 $LOGIN_BUTTON->set_text ("Logout");
316 root 1.49 $SETUP_DIALOG->hide;
317 root 1.23
318 root 1.1 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
319    
320 root 1.11 my ($host, $port) = split /:/, $CFG->{host};
321    
322 root 1.1 $MAP = new CFClient::Map $mapsize, $mapsize;
323    
324     $CONN = eval {
325 root 1.11 new CFClient::Protocol
326 root 1.1 host => $host,
327     port => $port || 13327,
328     user => $CFG->{user},
329     pass => $CFG->{password},
330     mapw => $mapsize,
331     maph => $mapsize,
332 root 1.11
333     map_widget => $MAPWIDGET,
334     logview => $LOGVIEW,
335     statusbox => $STATUSBOX,
336     map => $MAP,
337     mapmap => $MAPMAP,
338 root 1.60 query => \&server_query,
339 root 1.11
340     sound_play => sub {
341     my ($x, $y, $soundnum, $type) = @_;
342    
343     $SDL_MIXER
344     or return;
345    
346     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
347     or return;
348    
349     $chunk->play;
350     },
351 root 1.1 };
352    
353     if ($CONN) {
354     CFClient::lowdelay fileno $CONN->{fh};
355    
356     status "login successful";
357     } else {
358     status "unable to connect";
359     stop_game();
360     }
361     }
362    
363     sub stop_game {
364 root 1.23 $LOGIN_BUTTON->set_text ("Login");
365 root 1.53 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
366 root 1.49 $SETUP_DIALOG->show;
367 root 1.23 $INV_WINDOW->hide;
368    
369 root 1.1 return unless $CONN;
370    
371     status "connection closed";
372 root 1.23
373 root 1.60 destroy_query_dialog $CONN;
374 root 1.1 $CONN->destroy;
375     $CONN = 0; # false, does not autovivify
376     }
377    
378 root 1.49 sub graphics_setup {
379     my $vbox = new CFClient::UI::VBox;
380 root 1.30
381 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
382    
383     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
384     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
385    
386 root 1.31 $hbox->add (my $mode_slider = new CFClient::UI::Slider force_w => $WIDTH * 0.1, expand => 1, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1]);
387 root 1.1 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
388    
389     $mode_slider->connect (changed => sub {
390     my ($self, $value) = @_;
391    
392     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
393     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
394     });
395     $mode_slider->emit (changed => $mode_slider->{range}[0]);
396    
397     my $row = 1;
398    
399     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
400     $table->add (1, $row++, new CFClient::UI::CheckBox
401     state => $CFG->{fullscreen},
402     tooltip => "Bring the client into fullscreen mode.",
403 root 1.18 on_changed => sub {
404 root 1.1 my ($self, $value) = @_;
405     $CFG->{fullscreen} = $value;
406     }
407     );
408    
409     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
410     $table->add (1, $row++, new CFClient::UI::CheckBox
411     state => $CFG->{fast},
412     tooltip => "Lower the visual quality considerably to speed up rendering.",
413 root 1.18 on_changed => sub {
414 root 1.1 my ($self, $value) = @_;
415     $CFG->{fast} = $value;
416     }
417     );
418    
419     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
420     $table->add (1, $row++, new CFClient::UI::Slider
421     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
422     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
423 root 1.18 on_changed => sub {
424 root 1.1 my ($self, $value) = @_;
425     $CFG->{map_scale} = 2 ** $value;
426     }
427     );
428    
429     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
430     $table->add (1, $row++, new CFClient::UI::CheckBox
431     state => $CFG->{fow_enable},
432     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
433 root 1.18 on_changed => sub {
434 root 1.1 my ($self, $value) = @_;
435     $CFG->{fow_enable} = $value;
436     }
437     );
438    
439     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
440     $table->add (1, $row++, new CFClient::UI::Slider
441     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
442     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
443 root 1.18 on_changed => sub {
444 root 1.1 my ($self, $value) = @_;
445     $CFG->{fow_intensity} = $value;
446     }
447     );
448    
449     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
450     $table->add (1, $row++, new CFClient::UI::CheckBox
451     state => $CFG->{fow_smooth},
452     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
453 root 1.18 on_changed => sub {
454 root 1.1 my ($self, $value) = @_;
455     $CFG->{fow_smooth} = $value;
456 root 1.15 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::OpenGL::GL_VERSION < 1.2;
457 root 1.1 }
458     );
459    
460     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
461     $table->add (1, $row++, new CFClient::UI::Slider
462     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
463     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
464 root 1.18 on_changed => sub { $CFG->{gui_fontsize} = $_[1] },
465 root 1.1 );
466    
467     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
468     $table->add (1, $row++, new CFClient::UI::Slider
469     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
470     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
471 root 1.18 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
472 root 1.1 );
473    
474     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
475    
476     $table->add (1, $row++, new CFClient::UI::Slider
477     range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 0.1],
478     tooltip => "The font size used by the <b>statistics window</b> only. Changes are instant.",
479 root 1.18 on_changed => sub {
480 root 1.1 $CFG->{stat_fontsize} = $_[1];
481     &set_stats_window_fontsize;
482     }
483     );
484    
485     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
486     $table->add (1, $row++, new CFClient::UI::Slider
487     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
488     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
489 root 1.18 on_changed => sub {
490 root 1.1 $CFG->{gauge_fontsize} = $_[1];
491     &set_gauge_window_fontsize;
492     }
493     );
494    
495     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
496     $table->add (1, $row++, new CFClient::UI::Slider
497 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
498     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
499     on_changed => sub {
500 root 1.1 $CFG->{gauge_size} = $_[1];
501     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
502     }
503     );
504    
505     $table->add (1, $row++, new CFClient::UI::Button
506     expand => 1, align => 0, text => "Apply",
507     tooltip => "Apply the video settings",
508 root 1.18 on_activate => sub {
509 root 1.1 video_shutdown ();
510     video_init ();
511     }
512     );
513    
514 root 1.49 $vbox
515     }
516    
517     sub audio_setup {
518     my $vbox = new CFClient::UI::VBox;
519    
520     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
521    
522     my $row = 0;
523    
524 root 1.1 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
525     $table->add (1, $row++, new CFClient::UI::CheckBox
526     state => $CFG->{audio_enable},
527     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.",
528 root 1.18 on_changed => sub {
529 root 1.1 $CFG->{audio_enable} = $_[1];
530     }
531     );
532     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
533 root 1.18 # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
534 root 1.1 # $CFG->{effects_volume} = $_[1];
535     # });
536     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
537     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
538     $hbox->add (new CFClient::UI::CheckBox
539     expand => 1, state => $CFG->{bgm_enable},
540     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
541 root 1.18 on_changed => sub {
542 root 1.1 $CFG->{bgm_enable} = $_[1];
543     }
544     );
545     $hbox->add (new CFClient::UI::Slider
546     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
547     tooltip => "The volume of the background music. Changes are instant.",
548 root 1.18 on_changed => sub {
549 root 1.1 $CFG->{bgm_volume} = $_[1];
550     CFClient::MixMusic::volume $_[1] * 128;
551     }
552     );
553    
554     $table->add (1, $row++, new CFClient::UI::Button
555     expand => 1, align => 0, text => "Apply",
556     tooltip => "Apply the audio settings",
557 root 1.18 on_activate => sub {
558 root 1.1 audio_shutdown ();
559     audio_init ();
560     }
561     );
562    
563 root 1.49 $vbox
564 root 1.1 }
565    
566     sub set_stats_window_fontsize {
567     for (values %{$STATWIDS}) {
568     $_->set_fontsize ($::CFG->{stat_fontsize});
569     }
570     }
571    
572     sub set_gauge_window_fontsize {
573     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
574     $_->set_fontsize ($::CFG->{gauge_fontsize});
575     }
576     }
577    
578     sub make_gauge_window {
579     my $gh = int $HEIGHT * $CFG->{gauge_size};
580    
581     my $win = new CFClient::UI::Frame (
582 root 1.30 force_x => 0,
583     force_y => "max",
584     force_w => $WIDTH,
585     force_h => $gh,
586 root 1.1 );
587    
588     $win->add (my $hbox = new CFClient::UI::HBox
589     children => [
590     (new CFClient::UI::HBox expand => 1),
591     (new CFClient::UI::VBox children => [
592     (new CFClient::UI::Empty expand => 1),
593 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
594 root 1.1 ]),
595     (my $vbox = new CFClient::UI::VBox),
596     ],
597     );
598    
599     $vbox->add (new CFClient::UI::HBox
600     expand => 1,
601     children => [
602     (new CFClient::UI::Empty expand => 1),
603     (my $hb = new CFClient::UI::HBox),
604     ],
605     );
606    
607     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
608     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.");
609     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
610     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.");
611     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
612     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.");
613     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
614     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.");
615    
616     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
617     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.");
618     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
619     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
620    
621     $GAUGES = {
622     exp => $exp, win => $win, range => $rng,
623     food => $fg, mana => $mg, hp => $hg, grace => $gg
624     };
625    
626     &set_gauge_window_fontsize;
627    
628     $win
629     }
630    
631 root 1.65 sub debug_setup {
632     my $table = new CFClient::UI::Table;
633    
634     $table->add (0, 0, new CFClient::UI::Label text => "Widget Borders");
635     $table->add (1, 0, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1 });
636     $table->add (0, 1, new CFClient::UI::Label text => "Tooltip Widget Info");
637     $table->add (1, 1, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2 });
638     $table->add (0, 2, new CFClient::UI::Label text => "Show FPS");
639     $table->add (1, 2, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4 });
640     $table->add (0, 3, new CFClient::UI::Label text => "Suppress Tooltips");
641     $table->add (1, 3, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8 });
642    
643     my @default_smooth = (0.05, 0.13, 0.05, 0.13, 0.30, 0.13, 0.05, 0.13, 0.05);
644    
645     for my $x (0..2) {
646     for my $y (0 .. 2) {
647     $table->add ($x + 3, $y,
648     new CFClient::UI::Entry
649     text => $default_smooth[$x * 3 + $y],
650     on_changed => sub { $MAP->{smooth_matrix}[$x * 3 + $y] = $_[1] if $MAP; 0 },
651     );
652     }
653     }
654    
655    
656     $table
657     }
658 elmex 1.24
659 root 1.60 sub stats_window {
660 elmex 1.19 my $tgw = new CFClient::UI::FancyFrame
661 root 1.30 y => $HEIGHT * (2/8),
662     x => "max",
663 elmex 1.19 title => "Stats",
664 root 1.30 name => "stats_window";
665 root 1.1
666     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
667     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
668     can_hover => 1, can_events => 1,
669     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
670     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
671     can_hover => 1, can_events => 1,
672     tooltip => "The map you are currently on (if supported by the server).");
673    
674 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
675     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
676     can_hover => 1, can_events => 1,
677 root 1.15 tooltip => "The weight of the player including all inventory items.");
678 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
679     can_hover => 1, can_events => 1,
680 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
681 elmex 1.5
682    
683 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
684     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
685    
686     my $color2 = [1, 1, 0];
687    
688     for (
689     [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"],
690     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
691     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
692     [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"],
693     [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"],
694     [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"],
695     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
696    
697     [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."],
698     [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."],
699     [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."],
700     [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."],
701     [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."],
702     [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."],
703     ) {
704     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
705    
706     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
707     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
708     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
709     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
710     }
711    
712     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
713    
714     my $row = 0;
715     my $col = 0;
716    
717     my %resist_names = (
718     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.)",
719     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.)",
720     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
721     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
722     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
723     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
724     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
725     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
726     pois => "<b>Poison</b> (resistance to getting poisoned)",
727     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
728     deat => "<b>Death</b> (resistance against death spells)",
729     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
730     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
731     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)",
732     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
733     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
734     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
735     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
736     );
737     for (qw/slow holyw conf fire depl magic
738     drain acid pois para deat phys
739     blind fear tund elec cold ghit/)
740     {
741     $tbl2->add ($col, $row,
742     $STATWIDS->{"res_$_"} =
743     new CFClient::UI::Label
744     font => $FONT_FIXED,
745     template => "-100%",
746     align => +1,
747     valign => 0,
748     can_events => 1,
749     can_hover => 1,
750     tooltip => $resist_names{$_},
751     );
752     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
753     font => $FONT_FIXED,
754     can_hover => 1,
755     can_events => 1,
756     image => "ui/resist/resist_$_.png",
757     tooltip => $resist_names{$_},
758     );
759    
760     $row++;
761     if ($row % 6 == 0) {
762     $col += 2;
763     $row = 0;
764     }
765     }
766    
767     &set_stats_window_fontsize;
768     update_stats_window ({});
769    
770     $tgw
771     }
772    
773 root 1.48 sub formsep($) {
774     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
775 root 1.1 }
776    
777     sub update_stats_window {
778     my ($stats) = @_;
779    
780 root 1.12 # I love text protocols...
781    
782     my $hp = $stats->{+CS_STAT_HP} * 1;
783     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
784     my $sp = $stats->{+CS_STAT_SP} * 1;
785     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
786     my $fo = $stats->{+CS_STAT_FOOD} * 1;
787 root 1.1 my $fo_m = 999;
788 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
789     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
790 root 1.1
791     $GAUGES->{hp} ->set_value ($hp, $hp_m);
792     $GAUGES->{mana} ->set_value ($sp, $sp_m);
793     $GAUGES->{food} ->set_value ($fo, $fo_m);
794     $GAUGES->{grace} ->set_value ($gr, $gr_m);
795 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
796     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
797     my $rng = $stats->{+CS_STAT_RANGE};
798 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
799     $GAUGES->{range} ->set_text ("Rng: " . $rng);
800 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
801 root 1.1 $title =~ s/^Player: //;
802     $STATWIDS->{title} ->set_text ("Title: " . $title);
803    
804 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
805     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
806     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
807     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
808     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
809     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
810     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
811     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
812     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
813     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
814     $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_ARMOUR});
815     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
816     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
817 root 1.1
818 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
819 elmex 1.5
820 root 1.12 # TODO: replace by CS_STAT_RES_xxx constants
821 root 1.1 my %tbl = (
822     phys => 100,
823     magic => 101,
824     fire => 102,
825     elec => 103,
826     cold => 104,
827     conf => 105,
828     acid => 106,
829     drain => 107,
830     ghit => 108,
831     pois => 109,
832     slow => 110,
833     para => 111,
834     tund => 112,
835     fear => 113,
836     depl => 113,
837     deat => 115,
838     holyw => 116,
839 root 1.12 blind => 117,
840 root 1.1 );
841    
842 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
843     for keys %tbl;
844 root 1.1 }
845    
846     my $METASERVER_ATIME;
847    
848     sub update_metaserver {
849     return if $METASERVER_ATIME > time;
850     $METASERVER_ATIME = time + 60;
851    
852     my $table = $METASERVER->{table};
853     $table->clear;
854     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
855    
856     my $buf;
857    
858     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
859    
860     unless ($fh) {
861     $label->set_text ("unable to contact metaserver: $!");
862     return;
863     }
864    
865     Event->io (fd => $fh, poll => 'r', cb => sub {
866     my $res = sysread $fh, $buf, 8192, length $buf;
867    
868     if (!defined $res) {
869     $_[0]->w->cancel;
870     $label->set_text ("error while retrieving server list: $!");
871     } elsif ($res == 0) {
872     $_[0]->w->cancel;
873     status "server list retrieved";
874    
875     utf8::decode $buf if utf8::valid $buf;
876    
877     $table->clear;
878    
879 root 1.62 my @tip = (
880     "The current number of users logged in on the server.",
881     "The hostname of the server.",
882     "The time this server has been running without being restarted.",
883     "The server software version - a '+' indicates a Crossfire+ server.",
884     "Short information about this server provided by its admins.",
885     );
886     my @col = qw(#Users Host Uptime Version Description);
887     $table->add ($_, 0, new CFClient::UI::Label
888     can_hover => 1, can_events => 1,
889     align => 0, fg => [1, 1, 0],
890     text => $col[$_], tooltip => $tip[$_])
891     for 0 .. $#col;
892 root 1.1
893     my @align = qw(1 0 1 1 -1);
894    
895     my $y = 0;
896     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
897     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
898    
899     for ($desc) {
900     s/<br>/\n/gi;
901     s/<li>/\n· /gi;
902     s/<.*?>//sgi;
903     s/&/&amp;/g;
904     s/</&lt;/g;
905     s/>/&gt;/g;
906     }
907    
908     $uptime = sprintf "%dd %02d:%02d:%02d",
909     (int $m->[8] / 86400),
910     (int $m->[8] / 3600) % 24,
911     (int $m->[8] / 60) % 60,
912     $m->[8] % 60;
913    
914     $m = [$users, $host, $uptime, $version, $desc];
915    
916     $y++;
917    
918 root 1.62 $table->add (scalar @$m, $y, new CFClient::UI::VBox children => [
919     (new CFClient::UI::Button
920     text => "Use",
921     tooltip => "Put this server into the <b>Host:Port</b> field",
922     on_activate => sub {
923     $HOST_ENTRY->set_text ($CFG->{host} = $host);
924     $METASERVER->hide;
925     },
926     ),
927 root 1.1 (new CFClient::UI::Empty expand => 1),
928     ]);
929    
930 root 1.62 $table->add ($_, $y, new CFClient::UI::Label
931     ellipsise => 0,
932     align => $align[$_],
933     text => $m->[$_],
934     tooltip => $tip[$_],
935     can_hover => 1,
936     can_events => 1,
937     fontsize => 0.8)
938 root 1.1 for 0 .. $#$m;
939     }
940     }
941     });
942     }
943    
944 root 1.40 sub metaserver_dialog {
945     my $dialog = new CFClient::UI::FancyFrame
946 root 1.62 title => "Server List",
947     name => 'metaserver_dialog',
948     x => 'center',
949     y => 'center',
950     z => 3,
951     force_h => $::HEIGHT * 0.4,
952     child => (my $vbox = new CFClient::UI::VBox),
953 root 1.40 on_visibility_change => sub {
954     update_metaserver if $_[1];
955     },
956     ;
957    
958 root 1.62 $dialog->{table} = new CFClient::UI::Table;
959    
960     $vbox->add (new CFClient::UI::ScrolledWindow expand => 1, child => $dialog->{table});
961 root 1.40
962     $dialog
963     }
964    
965 root 1.1 sub server_setup {
966 root 1.49 my $vbox = new CFClient::UI::VBox;
967 elmex 1.19
968 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
969     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
970    
971     {
972     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
973    
974     $vbox->add (
975 root 1.40 $HOST_ENTRY = new CFClient::UI::Entry
976 root 1.1 expand => 1,
977     text => $CFG->{host},
978     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
979 root 1.18 on_changed => sub {
980 root 1.1 my ($self, $value) = @_;
981     $CFG->{host} = $value;
982     }
983     );
984    
985     $METASERVER = metaserver_dialog;
986    
987 root 1.40 $vbox->add (new CFClient::UI::Button
988     expand => 1,
989     text => "Server List",
990     other => $METASERVER,
991 root 1.1 tooltip => "Show a list of available crossfire servers",
992 root 1.40 on_activate => sub { $METASERVER->toggle_visibility },
993 root 1.62 on_visibility_change => sub { $METASERVER->hide unless $_[1] },
994 root 1.1 );
995     }
996    
997     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
998     $table->add (1, 4, new CFClient::UI::Entry
999     text => $CFG->{user},
1000     tooltip => "The name of your character on the server",
1001 root 1.18 on_changed => sub {
1002 root 1.1 my ($self, $value) = @_;
1003     $CFG->{user} = $value;
1004     }
1005     );
1006    
1007     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
1008     $table->add (1, 5, new CFClient::UI::Entry
1009     text => $CFG->{password},
1010     hidden => 1,
1011     tooltip => "The password for your character",
1012 root 1.18 on_changed => sub {
1013 root 1.1 my ($self, $value) = @_;
1014     $CFG->{password} = $value;
1015     }
1016     );
1017    
1018     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
1019     $table->add (1, 7, new CFClient::UI::Slider
1020 root 1.30 force_w => 100,
1021 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1022     tooltip => "This is the size of the portion of the map update the server sends you. "
1023     . "If you set this to a high value you will be able to see further, "
1024     . "but you also increase bandwidth requirements and latency. "
1025     . "This option is only used once at log-in.",
1026 root 1.18 on_changed => sub {
1027 root 1.1 my ($self, $value) = @_;
1028    
1029     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
1030     },
1031     );
1032    
1033     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1034     $table->add (1, 8, new CFClient::UI::CheckBox
1035     state => $CFG->{face_prefetch},
1036     tooltip => "<b>Background Image Prefetch</b>\n\n"
1037     . "If enabled, the client automatically pre-fetches images from the server. "
1038     . "This might increase or create lag, but increases the chances "
1039     . "of faces being ready for display when you encounter them. "
1040     . "It also uses up server bandwidth on every connect, "
1041     . "so only set it if you really need to prefetch images. "
1042     . "This option can be set and unset any time.",
1043 root 1.18 on_changed => sub { $CFG->{face_prefetch} = $_[1] },
1044 root 1.1 );
1045    
1046     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
1047     $table->add (1, 9, new CFClient::UI::Entry
1048     text => $CFG->{output_count},
1049     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1050 root 1.18 on_changed => sub { $CFG->{output_count} = $_[1] },
1051 root 1.1 );
1052    
1053     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
1054     $table->add (1, 10, new CFClient::UI::Entry
1055     text => $CFG->{output_sync},
1056     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1057 root 1.18 on_changed => sub { $CFG->{output_sync} = $_[1] },
1058 root 1.1 );
1059    
1060     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
1061     expand => 1,
1062     align => 0,
1063     text => "Login",
1064 root 1.18 on_activate => sub {
1065 root 1.1 $CONN ? stop_game
1066     : start_game;
1067     },
1068     );
1069    
1070 root 1.49 $table->add (0, 12, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
1071     $table->add (1, 12, my $saycmd = new CFClient::UI::Entry
1072     text => $CFG->{say_command},
1073     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. "
1074     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
1075     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
1076     on_changed => sub {
1077     my ($self, $value) = @_;
1078     $CFG->{say_command} = $value;
1079     }
1080     );
1081    
1082 root 1.67 $vbox->add (new CFClient::UI::Label
1083     text => "Server Info",
1084     fontsize => 1.2,
1085     padding_y => 8,
1086     fg => [1, 1, 0, 1],
1087     );
1088    
1089     $vbox->add ($SERVER_INFO = new CFClient::UI::Label ellipsise => 0);
1090    
1091 root 1.49 $vbox
1092 root 1.1 }
1093    
1094     sub message_window {
1095     my $window = new CFClient::UI::FancyFrame
1096 elmex 1.16 name => "message_window",
1097 root 1.1 title => "Messages",
1098     border_bg => [1, 1, 1, 1],
1099     bg => [0, 0, 0, 0.75],
1100 root 1.30 x => "max",
1101     y => 0,
1102 root 1.60 force_w => $::WIDTH * 0.4,
1103     force_h => $::HEIGHT * 0.5,
1104 root 1.1 child => (my $vbox = new CFClient::UI::VBox);
1105    
1106     $vbox->add ($LOGVIEW);
1107    
1108     $vbox->add (my $input = new CFClient::UI::Entry
1109     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
1110     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
1111     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
1112     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
1113 root 1.18 on_focus_in => sub {
1114 root 1.1 my ($input, $prev_focus) = @_;
1115    
1116     delete $input->{refocus_map};
1117    
1118     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
1119     $input->{refocus_map} = 1;
1120     }
1121     delete $input->{auto_activated};
1122     },
1123 root 1.18 on_activate => sub {
1124 root 1.1 my ($input, $text) = @_;
1125     $input->set_text ('');
1126    
1127 elmex 1.46 if ($text =~ /^\/(.*)/) {
1128 root 1.1 $::CONN->user_send ($1);
1129     } else {
1130     my $say_cmd = $::CFG->{say_command} || 'say';
1131     $::CONN->user_send ("$say_cmd $text");
1132     }
1133     if ($input->{refocus_map}) {
1134     delete $input->{refocus_map};
1135     $MAPWIDGET->focus_in
1136     }
1137     },
1138 root 1.18 on_escape => sub {
1139 root 1.1 $MAPWIDGET->focus_in
1140     },
1141     );
1142    
1143     $CONSOLE = {
1144     window => $window,
1145 root 1.30 input => $input,
1146 root 1.1 };
1147    
1148     $window
1149     }
1150    
1151     sub open_quit_dialog {
1152     unless ($QUIT_DIALOG) {
1153 root 1.30 $QUIT_DIALOG = new CFClient::UI::FancyFrame
1154     x => "center",
1155     y => "center",
1156 root 1.55 z => 50,
1157 root 1.30 title => "Really Quit?",
1158     ;
1159 root 1.1
1160     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
1161    
1162     $vb->add (new CFClient::UI::Label
1163     text => "You should find a savebed and apply it first!",
1164     max_w => $WIDTH * 0.25,
1165     ellipsize => 0,
1166     );
1167     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
1168     $hb->add (new CFClient::UI::Button
1169     text => "Ok",
1170     expand => 1,
1171 root 1.18 on_activate => sub { $QUIT_DIALOG->hide },
1172 root 1.1 );
1173     $hb->add (new CFClient::UI::Button
1174     text => "Quit anyway",
1175     expand => 1,
1176 root 1.18 on_activate => sub { exit },
1177 root 1.1 );
1178 root 1.21 }
1179 root 1.1
1180 root 1.21 $QUIT_DIALOG->show;
1181 root 1.1 }
1182    
1183 root 1.49 sub autopickup_setup {
1184 root 1.51 my $table = new CFClient::UI::Table;
1185 elmex 1.44
1186 elmex 1.43 for (
1187 root 1.51 ["General", 0, 0,
1188 root 1.58 ["Enable autopickup" => PICKUP_NEWMODE],
1189     ["Inhibit autopickup" => PICKUP_INHIBIT],
1190     ["Stop before pickup" => PICKUP_STOP],
1191     ["Debug autopickup" => PICKUP_DEBUG],
1192 root 1.51 ],
1193     ["Weapons", 0, 6,
1194 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1195     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1196     ["Bows" => PICKUP_BOW],
1197     ["Arrows" => PICKUP_ARROW],
1198 root 1.51 ],
1199     ["Armour", 0, 12,
1200 root 1.58 ["Helmets" => PICKUP_HELMET],
1201     ["Shields" => PICKUP_SHIELD],
1202     ["Body Armour" => PICKUP_ARMOUR],
1203     ["Boots" => PICKUP_BOOTS],
1204     ["Gloves" => PICKUP_GLOVES],
1205     ["Cloaks" => PICKUP_CLOAK],
1206 root 1.51 ],
1207    
1208     ["Readables", 2, 2,
1209 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1210     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1211     ["Normal Books/Scrolls" => PICKUP_READABLES],
1212 root 1.51 ],
1213     ["Misc", 2, 7,
1214 root 1.58 ["Food" => PICKUP_FOOD],
1215     ["Drinks" => PICKUP_DRINK],
1216     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1217     ["Keys" => PICKUP_KEY],
1218     ["Magical Items" => PICKUP_MAGICAL],
1219     ["Potions" => PICKUP_POTION],
1220     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1221     ["Ignore cursed" => PICKUP_NOT_CURSED],
1222     ["Jewelery" => PICKUP_JEWELS],
1223 root 1.51 ],
1224 elmex 1.66 ["Weight/Value ratio", 2, 17]
1225 elmex 1.43 )
1226     {
1227 root 1.51 my ($title, $x, $y, @bits) = @$_;
1228     $table->add ($x, $y, new CFClient::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1229    
1230     for (@bits) {
1231     ++$y;
1232    
1233 elmex 1.43 my $mask = $_->[1];
1234 root 1.51 $table->add ($x , $y, new CFClient::UI::Label text => $_->[0], align => 1, expand => 1);
1235     $table->add ($x+1, $y, new CFClient::UI::CheckBox
1236 elmex 1.43 state => $CFG->{pickup} & $mask,
1237     on_changed => sub {
1238     my ($box, $value) = @_;
1239 root 1.63
1240 elmex 1.43 if ($value) {
1241 elmex 1.45 $::CFG->{pickup} |= $mask;
1242 elmex 1.43 } else {
1243 root 1.63 $::CFG->{pickup} &= ~$mask;
1244 elmex 1.43 }
1245 root 1.63
1246     $::CONN->send_command ("pickup $::CFG->{pickup}")
1247 elmex 1.45 if defined $::CONN;
1248 elmex 1.43 });
1249     }
1250     }
1251    
1252 elmex 1.66 $table->add (2, 18, new CFClient::UI::ValSlider
1253     range => [0, 0, 16, 1, 1],
1254     to_value => sub { ">= " . 5 * $_[0] },
1255     on_changed => sub {
1256     my ($slider, $value) = @_;
1257    
1258     $::CFG->{pickup} &= ~0x7;
1259     $::CFG->{pickup} |= int $value
1260     if $value;
1261     1;
1262     });
1263     $table->add (3, 18, new CFClient::UI::Button
1264     text => "set",
1265     on_activate => sub {
1266     $::CONN->send_command ("pickup $::CFG->{pickup}")
1267     if defined $::CONN;
1268     1;
1269     });
1270    
1271 root 1.51 $table
1272 elmex 1.43 }
1273    
1274 root 1.60 sub inventory_window {
1275 root 1.23 my $invwin = $INV_WINDOW = new CFClient::UI::FancyFrame
1276 root 1.32 x => "center",
1277     y => "center",
1278     force_w => $WIDTH * 9/10,
1279     force_h => $HEIGHT * 9/10,
1280     title => "Inventory",
1281 root 1.21 ;
1282 root 1.1
1283 root 1.21 $invwin->add (my $hb = new CFClient::UI::HBox homogeneous => 1);
1284 root 1.1
1285 root 1.21 $hb->add (my $vb1 = new CFClient::UI::VBox);
1286     $vb1->add (new CFClient::UI::Label align => 0, text => "Player");
1287     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
1288 root 1.1
1289 root 1.21 $hb->add (my $vb2 = new CFClient::UI::VBox);
1290 elmex 1.17
1291 elmex 1.27 $vb2->add ($INV_RIGHT_HB = new CFClient::UI::HBox);
1292 elmex 1.14
1293 root 1.1 $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
1294    
1295 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1296     CFClient::Protocol::set_opencont ($::CONN, 0, "Floor");
1297    
1298 root 1.1 $invwin
1299     }
1300    
1301 root 1.49 sub spell_setup {
1302     new CFClient::UI::SpellList
1303 elmex 1.38 }
1304    
1305 root 1.49 sub keyboard_setup {
1306 elmex 1.24 my $binding_list = new CFClient::UI::VBox;
1307    
1308 elmex 1.34 my $refresh;
1309     $refresh = sub {
1310 elmex 1.24 $binding_list->clear ();
1311    
1312     for my $mod (keys %{$::CFG->{bindings}}) {
1313     for my $sym (keys %{$::CFG->{bindings}->{$mod}}) {
1314     my $cmds = $::CFG->{bindings}->{$mod}->{$sym};
1315     next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1316    
1317     my $lbl = join "; ", @$cmds;
1318 elmex 1.34 my $nam = CFClient::Binder::keycombo_to_name ($mod, $sym);
1319 elmex 1.24 $binding_list->add (my $hb = new CFClient::UI::HBox);
1320     $hb->add (new CFClient::UI::Button
1321 elmex 1.25 text => "delete",
1322 elmex 1.34 tooltip => "Deletes the binding",
1323 elmex 1.24 on_activate => sub {
1324     $binding_list->remove ($hb);
1325     delete $::CFG->{bindings}->{$mod}->{$sym};
1326     });
1327 elmex 1.34
1328     $hb->add (new CFClient::UI::Button
1329     text => "edit",
1330     tooltip => "Edits the binding",
1331     on_activate => sub {
1332     $::BIND_EDITOR->set_binding (
1333     $mod, $sym, $::CFG->{bindings}->{$mod}->{$sym},
1334     sub {
1335     my ($nmod, $nsym, $ncmds) = @_;
1336     delete $::CFG->{bindings}->{$mod}->{$sym};
1337     $::CFG->{bindings}->{$nmod}->{$nsym} = $ncmds;
1338     $refresh->();
1339 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1340     $SETUP_DIALOG->show;
1341 elmex 1.34 },
1342     sub {
1343 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1344     $SETUP_DIALOG->show;
1345 elmex 1.34 });
1346     $::BIND_EDITOR->show;
1347 root 1.49 $SETUP_DIALOG->hide;
1348 elmex 1.34 });
1349    
1350     $hb->add (new CFClient::UI::Label text => "(Key: $nam)");
1351 elmex 1.24 $hb->add (new CFClient::UI::Label text => $lbl, expand => 1);
1352     }
1353     }
1354     };
1355    
1356 root 1.49 my $vb = new CFClient::UI::VBox;
1357 elmex 1.35 $vb->add ($binding_list);
1358     $vb->add (my $hb = new CFClient::UI::HBox);
1359 root 1.49
1360 elmex 1.35 $hb->add (new CFClient::UI::Button
1361 elmex 1.34 text => "record new",
1362 elmex 1.35 expand => 1,
1363 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1364     on_activate => sub {
1365     $::BIND_EDITOR->set_binding (undef, undef, [],
1366     sub {
1367     my ($mod, $sym, $cmds) = @_;
1368     $::CFG->{bindings}->{$mod}->{$sym} = $cmds;
1369     $refresh->();
1370 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1371     $SETUP_DIALOG->show;
1372 elmex 1.34 },
1373     sub {
1374 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1375     $SETUP_DIALOG->show;
1376 root 1.53 },
1377     );
1378 root 1.49 $SETUP_DIALOG->hide;
1379 elmex 1.34 $::BIND_EDITOR->show;
1380     },
1381     );
1382 root 1.49
1383 elmex 1.35 $hb->add (new CFClient::UI::Button
1384     text => "close",
1385     tooltip => "Closes the binding window",
1386     expand => 1,
1387     on_activate => sub {
1388 root 1.49 $SETUP_DIALOG->hide;
1389 elmex 1.35 }
1390     );
1391    
1392 elmex 1.24 $refresh->();
1393 root 1.49
1394     $vb
1395 elmex 1.24 }
1396    
1397 root 1.64 sub help_window {
1398 root 1.1 my $win = new CFClient::UI::FancyFrame
1399 root 1.41 x => 'center',
1400     y => 'center',
1401 root 1.55 z => 2,
1402 root 1.41 name => 'doc_browser',
1403     force_w => int $WIDTH * 7/8,
1404     force_h => int $HEIGHT * 7/8,
1405     title => "Documentation";
1406 root 1.1
1407     $win->add (my $vbox = new CFClient::UI::VBox);
1408    
1409     $vbox->add (my $buttons = new CFClient::UI::HBox);
1410 root 1.64 $vbox->add (my $viewer = new CFClient::UI::TextScroller
1411     expand => 1, fontsize => 0.8, padding_x => 4);
1412 root 1.1
1413 root 1.64 $buttons->add (new CFClient::UI::Label text => "Choose a document to display: ");
1414     $buttons->add (my $combo = new CFClient::UI::Combobox
1415     value => undef,
1416     options => [
1417     [intro => "Introduction"],
1418     [manual => "Manual"],
1419     [skill_help => "Skills"],
1420     [command_help => "Commands"],
1421     [dmcommand_help => "DM Commands"],
1422     [COPYING => "License Terms"],
1423     ],
1424     on_changed => sub {
1425     my ($self, $pod) = @_;
1426 root 1.1
1427 root 1.64 my $pom = CFClient::load_pod CFClient::find_rcfile "pod/$pod.pod",
1428     doc_viewer => 1, sub { CFClient::pod_to_pango_list $_[0] };
1429 root 1.1
1430 root 1.64 $viewer->clear;
1431    
1432     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
1433     for @$pom;
1434 root 1.1
1435 root 1.64 $viewer->set_offset (0);
1436     },
1437     on_visibility_change => sub {
1438     my ($self, $visible) = @_;
1439     return unless $visible;
1440     return if $self->{value};
1441     $self->set_value ("intro");
1442     },
1443     );
1444 root 1.1
1445     $win
1446     }
1447    
1448     sub sdl_init {
1449     CFClient::SDL_Init
1450     and die "SDL::Init failed!\n";
1451     }
1452    
1453     sub video_init {
1454     sdl_init;
1455    
1456     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1457    
1458     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1459    
1460     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1461     $FULLSCREEN = $CFG->{fullscreen};
1462     $FAST = $CFG->{fast};
1463    
1464     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1465     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1466    
1467     $SDL_ACTIVE = 1;
1468     $LAST_REFRESH = time - 0.01;
1469    
1470 root 1.10 CFClient::OpenGL::init;
1471 root 1.1
1472     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1473    
1474     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1475    
1476     #############################################################################
1477    
1478     if ($DEBUG_STATUS) {
1479     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1480     } else {
1481     # create the widgets
1482    
1483 root 1.30 $DEBUG_STATUS = new CFClient::UI::Label
1484     padding => 0,
1485     z => 100,
1486     force_x => "max",
1487     force_y => 0;
1488 root 1.1 $DEBUG_STATUS->show;
1489 elmex 1.34
1490     $BIND_EDITOR = new CFClient::UI::BindEditor (x => "max", y => 0);
1491    
1492 root 1.1 $STATUSBOX = new CFClient::UI::Statusbox;
1493 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1494 root 1.1
1495     (new CFClient::UI::Frame
1496     bg => [0, 0, 0, 0.4],
1497 root 1.30 force_x => 0,
1498     force_y => "max",
1499 root 1.1 child => $STATUSBOX,
1500     )->show;
1501    
1502     CFClient::UI::FancyFrame->new (
1503 root 1.47 title => "Map",
1504 root 1.42 name => "mapmap",
1505 root 1.30 x => 0,
1506     y => $FONTSIZE + 8,
1507 root 1.1 border_bg => [1, 1, 1, 192/255],
1508     bg => [1, 1, 1, 0],
1509     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1510     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1511     ),
1512     )->show;
1513    
1514     $MAPWIDGET = new CFClient::MapWidget;
1515     $MAPWIDGET->connect (activate_console => sub {
1516     my ($mapwidget, $preset) = @_;
1517    
1518     if ($CONSOLE) {
1519     $CONSOLE->{input}->{auto_activated} = 1;
1520     $CONSOLE->{input}->focus_in;
1521    
1522     if ($preset && $CONSOLE->{input}->get_text eq '') {
1523     $CONSOLE->{input}->set_text ($preset);
1524     }
1525     }
1526     });
1527     $MAPWIDGET->show;
1528     $MAPWIDGET->focus_in;
1529    
1530 root 1.64 $LOGVIEW = new CFClient::UI::TextScroller
1531 root 1.1 expand => 1,
1532     font => $FONT_FIXED,
1533     fontsize => $::CFG->{log_fontsize},
1534 root 1.61 indent => -4,
1535 root 1.1 can_hover => 1,
1536     can_events => 1,
1537     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1538     ;
1539    
1540 root 1.49 $SETUP_DIALOG = new CFClient::UI::FancyFrame
1541     title => "Setup",
1542     name => "setup_dialog",
1543     x => 'center',
1544     y => 'center',
1545 root 1.53 z => 2,
1546 root 1.49 force_w => $::WIDTH * 0.6,
1547     force_h => $::HEIGHT * 0.6,
1548     ;
1549    
1550 root 1.50 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFClient::UI::Notebook expand => 1, debug => 1,
1551 root 1.60 filter => new CFClient::UI::ScrolledWindow expand => 1, scroll_y => 1);
1552 root 1.49
1553     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1554     "Configure the server to play on, your username, password and other server-related options.");
1555     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1556 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1557 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1558     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1559     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1560     "Configure the use of audio, sound effects and background music.");
1561     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1562     "Lets you define, edit and delete bindings."
1563 root 1.54 . "There is a shortcut for making bindings: <b>Left Control + Insert</b> opens the binding editor "
1564 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1565 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1566 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1567     . "binding editor closes");
1568     $SETUP_NOTEBOOK->add (Spells => $SETUP_SPELLS = spell_setup,
1569     "Displays all spells you have and lets you edit keyboard shortcuts for them.");
1570 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1571     "Some debuggign options. Do not ask.");
1572 root 1.49
1573 root 1.57 $BUTTONBAR = new CFClient::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1574 root 1.1
1575 root 1.49 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1576     tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1577    
1578 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1579 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1580    
1581     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
1582    
1583 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => $STATS_WINDOW = stats_window,
1584 root 1.1 tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1585 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => inventory_window,
1586 root 1.51 tooltip => "Toggles the inventory window, where you can manage your loot (or treasures :). "
1587 root 1.52 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory.");
1588 root 1.1
1589     $BUTTONBAR->add (new CFClient::UI::Button
1590     text => "Save Config",
1591     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1592 root 1.18 on_activate => sub {
1593 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1594 root 1.28 CFClient::write_cfg "$Crossfire::VARDIR/cfplusrc";
1595 root 1.1 status "Configuration Saved";
1596     },
1597     );
1598    
1599 root 1.64 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => help_window,
1600 root 1.1 tooltip => "View Documentation");
1601    
1602     $BUTTONBAR->add (new CFClient::UI::Button
1603 root 1.18 text => "Quit",
1604     tooltip => "Terminates the program",
1605     on_activate => sub {
1606 root 1.1 if ($CONN) {
1607     open_quit_dialog;
1608     } else {
1609     exit;
1610     }
1611     },
1612     );
1613    
1614     $BUTTONBAR->show;
1615 root 1.49 $SETUP_DIALOG->show;
1616     }
1617 root 1.1
1618 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1619 root 1.1 }
1620    
1621     sub video_shutdown {
1622     undef $SDL_ACTIVE;
1623     }
1624    
1625     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1626     my $bgmusic;#TODO#hack#d#
1627    
1628     sub audio_channel_finished {
1629     my ($channel) = @_;
1630    
1631     #warn "channel $channel finished\n";#d#
1632     }
1633    
1634     sub audio_music_finished {
1635     return unless $CFG->{bgm_enable};
1636    
1637     # TODO: hack, do play loop and mood music
1638     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1639     $bgmusic->play (0);
1640    
1641     push @bgmusic, shift @bgmusic;
1642     }
1643    
1644     sub audio_init {
1645     if ($CFG->{audio_enable}) {
1646     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1647     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1648    
1649     unless ($SDL_MIXER) {
1650     status "Unable to open sound device: there will be no sound";
1651     return;
1652     }
1653    
1654     CFClient::Mix_AllocateChannels 8;
1655     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1656    
1657     audio_music_finished;
1658    
1659     while (<$fh>) {
1660     next if /^\s*#/;
1661     next if /^\s*$/;
1662    
1663     my ($file, $volume, $event) = split /\s+/, $_, 3;
1664    
1665     push @SOUNDS, "$volume,$file";
1666    
1667     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1668     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1669     $chunk->volume ($volume * 128 / 100);
1670     $chunk
1671     };
1672     }
1673     } else {
1674     status "unable to open sound config: $!";
1675     }
1676     }
1677     }
1678    
1679     sub audio_shutdown {
1680     CFClient::Mix_CloseAudio if $SDL_MIXER;
1681     undef $SDL_MIXER;
1682     @SOUNDS = ();
1683     %AUDIO_CHUNKS = ();
1684     }
1685    
1686     my %animate_object;
1687     my $animate_timer;
1688    
1689     my $fps = 9;
1690    
1691     my %demo;#d#
1692    
1693     sub force_refresh {
1694     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1695 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1696 root 1.1
1697     $CFClient::UI::ROOT->draw;
1698    
1699     $WANT_REFRESH = 0;
1700     $CAN_REFRESH = 0;
1701     $LAST_REFRESH = $NOW;
1702    
1703     0 && do {
1704     # some weird model-drawing code, just a joke right now
1705     use CFClient::OpenGL;
1706    
1707     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1708     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1709     $demo{r} ||= do {
1710     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1711     $mod->{v} = pack "f*", @{$mod->{v}};
1712     $_ = [scalar @$_, pack "S!*", @$_]
1713     for values %{$mod->{g}};
1714     $mod
1715     };
1716    
1717     my $r = $demo{r} or die;
1718    
1719     glDepthMask 1;
1720     glClear GL_DEPTH_BUFFER_BIT;
1721     glEnable GL_TEXTURE_2D;
1722     glEnable GL_DEPTH_TEST;
1723     glEnable GL_CULL_FACE;
1724     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1725    
1726     glMatrixMode GL_PROJECTION;
1727     glLoadIdentity;
1728     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1729     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1730     glMatrixMode GL_MODELVIEW;
1731     glLoadIdentity;
1732    
1733     glPushMatrix;
1734     glTranslate 0, 0, -800;
1735     glScale 1, -1, 1;
1736     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1737     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1738     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1739     glScale 50, 50, 50;
1740    
1741     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1742     while (my ($k, $v) = each %{$r->{g}}) {
1743     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1744     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1745     }
1746    
1747     glPopMatrix;
1748    
1749     glShadeModel GL_FLAT;
1750     glDisable GL_DEPTH_TEST;
1751     glDisable GL_TEXTURE_2D;
1752     glDepthMask 0;
1753    
1754     $WANT_REFRESH++;
1755     };
1756    
1757     CFClient::SDL_GL_SwapBuffers;
1758     }
1759    
1760 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1761 root 1.1 $NOW = time;
1762    
1763     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1764     for CFClient::SDL_PollEvent;
1765    
1766     if (%animate_object) {
1767     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1768     $WANT_REFRESH++;
1769     }
1770    
1771     if ($WANT_REFRESH) {
1772     force_refresh;
1773     } else {
1774     $CAN_REFRESH = 1;
1775     }
1776     });
1777    
1778     sub animation_start {
1779     my ($widget) = @_;
1780     $animate_object{$widget} = $widget;
1781     }
1782    
1783     sub animation_stop {
1784     my ($widget) = @_;
1785     delete $animate_object{$widget};
1786     }
1787    
1788     # check once/second for faces that need to be prefetched
1789     # this should, of course, only run on demand, but
1790     # SDL forces worse things on us....
1791    
1792     Event->timer (after => 1, interval => 0.25, cb => sub {
1793     $CONN->face_prefetch
1794     if $CONN;
1795     });
1796    
1797     %SDL_CB = (
1798     CFClient::SDL_QUIT => sub {
1799     Event::unloop -1;
1800     },
1801     CFClient::SDL_VIDEORESIZE => sub {
1802     },
1803     CFClient::SDL_VIDEOEXPOSE => sub {
1804     CFClient::UI::full_refresh;
1805     },
1806     CFClient::SDL_ACTIVEEVENT => sub {
1807     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1808     },
1809     CFClient::SDL_KEYDOWN => sub {
1810     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1811     # alt-enter
1812     video_shutdown;
1813     $CFG->{fullscreen} = !$CFG->{fullscreen};
1814     video_init;
1815     } else {
1816     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1817     }
1818     },
1819     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1820     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1821     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1822     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1823     CFClient::SDL_USEREVENT => sub {
1824     if ($_[0]{code} == 1) {
1825     audio_channel_finished $_[0]{data1};
1826     } elsif ($_[0]{code} == 0) {
1827     audio_music_finished;
1828     }
1829     },
1830     );
1831    
1832     #############################################################################
1833    
1834     $SIG{INT} = $SIG{TERM} = sub { exit };
1835    
1836     {
1837 root 1.49 local $SIG{__DIE__} = sub {
1838     return unless defined $^S && !$^S;
1839     Carp::confess $_[1];#d#TODO: remove when stable
1840     CFClient::fatal $_[0];
1841     };
1842 root 1.1
1843 root 1.28 CFClient::read_cfg "$Crossfire::VARDIR/cfplusrc";
1844 elmex 1.16 CFClient::UI::set_layout ($::CFG->{layout});
1845 root 1.1
1846     my %DEF_CFG = (
1847     sdl_mode => 0,
1848     width => 640,
1849     height => 480,
1850     fullscreen => 0,
1851     fast => 0,
1852     map_scale => 1,
1853     fow_enable => 1,
1854     fow_intensity => 0.45,
1855     fow_smooth => 0,
1856     gui_fontsize => 1,
1857 root 1.60 log_fontsize => 0.7,
1858 root 1.1 gauge_fontsize=> 1,
1859     gauge_size => 0.35,
1860 root 1.60 stat_fontsize => 0.7,
1861 root 1.1 mapsize => 100,
1862     host => "crossfire.schmorp.de",
1863     say_command => 'say',
1864     audio_enable => 1,
1865     bgm_enable => 1,
1866     bgm_volume => 0.25,
1867     face_prefetch => 0,
1868     output_sync => 1,
1869     output_count => 1,
1870 root 1.63 pickup => 0,
1871 root 1.1 );
1872    
1873     while (my ($k, $v) = each %DEF_CFG) {
1874     $CFG->{$k} = $v unless exists $CFG->{$k};
1875     }
1876    
1877     sdl_init;
1878    
1879     @SDL_MODES = reverse
1880     grep $_->[0] >= 640 && $_->[1] >= 480,
1881     CFClient::SDL_ListModes;
1882    
1883     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1884    
1885     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1886    
1887     {
1888     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1889     DejaVuSans.ttf
1890     DejaVuSansMono.ttf
1891     DejaVuSans-Bold.ttf
1892     DejaVuSansMono-Bold.ttf
1893     DejaVuSans-Oblique.ttf
1894     DejaVuSansMono-Oblique.ttf
1895     DejaVuSans-BoldOblique.ttf
1896     DejaVuSansMono-BoldOblique.ttf
1897     );
1898    
1899     CFClient::add_font $_ for @fonts;
1900    
1901     CFClient::pango_init;
1902    
1903     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1904     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1905    
1906     $FONT_PROP->make_default;
1907     }
1908    
1909     # compare mono (ft) vs. rgba (cairo)
1910     # ft - 1.8s, cairo 3s, even in alpha-only mode
1911     # for my $rgba (0..1) {
1912     # my $t1 = Time::HiRes::time;
1913     # for (1..1000) {
1914     # my $layout = CFClient::Layout->new ($rgba);
1915     # $layout->set_text ("hallo" x 100);
1916     # $layout->render;
1917     # }
1918     # my $t2 = Time::HiRes::time;
1919     # warn $t2-$t1;
1920     # }
1921    
1922     video_init;
1923     audio_init;
1924     }
1925    
1926     Event::loop;
1927    
1928     END { CFClient::SDL_Quit }
1929    
1930     =head1 NAME
1931    
1932 root 1.28 cfplus - A Crossfire+ and Crossfire game client
1933 root 1.1
1934     =head1 SYNOPSIS
1935    
1936     Just run it - no commandline arguments are supported.
1937    
1938     =head1 USAGE
1939    
1940 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
1941 root 1.1 fullscreen and interactively.
1942    
1943 root 1.39 =head1 DEBUGGING
1944    
1945    
1946     CFPLUS_DEBUG - environment variable
1947    
1948     1 draw borders around widgets
1949     2 add low-level widget info to tooltips
1950     4 show fps
1951     8 suppress tooltips
1952    
1953 root 1.1 =head1 AUTHOR
1954    
1955     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1956    
1957    
1958