ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.133
Committed: Sun Dec 3 01:03:49 2006 UTC (17 years, 5 months ago) by root
Branch: MAIN
Changes since 1.132: +3 -2 lines
Log Message:
newpickup flesh

File Contents

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