ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.131
Committed: Sun Nov 19 20:31:29 2006 UTC (17 years, 6 months ago) by root
Branch: MAIN
Changes since 1.130: +3 -2 lines
Log Message:
*** empty log message ***

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     # do things only needed for single-binary version (par)
28     BEGIN {
29     if (%PAR::LibCache) {
30     @INC = grep ref, @INC; # weed out all paths except pars loader refs
31    
32     while (my ($filename, $zip) = each %PAR::LibCache) {
33     for ($zip->memberNames) {
34 root 1.122 next unless /^root\/(.*)/;
35 root 1.1 $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
36     unless -e "$ENV{PAR_TEMP}/$1";
37     }
38     }
39    
40     # TODO: pango-rc file, anybody?
41    
42     unshift @INC, $ENV{PAR_TEMP};
43     }
44     }
45    
46     # need to do it again because that pile of garbage called PAR nukes it before main
47     unshift @INC, $ENV{PAR_TEMP}
48     if %PAR::LibCache;
49    
50     use Time::HiRes 'time';
51     use Event;
52    
53     use Crossfire;
54 root 1.12 use Crossfire::Protocol::Constants;
55 root 1.1
56     use Compress::LZF;
57    
58 root 1.104 use CFPlus;
59     use CFPlus::OpenGL ();
60     use CFPlus::Protocol;
61     use CFPlus::UI;
62     use CFPlus::Pod;
63     use CFPlus::BindingEditor;
64     use CFPlus::MapWidget;
65 root 1.1
66 root 1.59 $SIG{QUIT} = sub { Carp::cluck "QUIT" };
67 root 1.91 $SIG{PIPE} = 'IGNORE';
68 root 1.59
69 root 1.103 $Event::Eval = 0;
70 root 1.1 $Event::DIED = sub {
71     # TODO: display dialog box or so
72 root 1.103 Carp::cluck $_[1];#d#TODO: remove when stable
73     return;#d#
74 root 1.119 CFPlus::fatal ($_[1]);
75 root 1.103 };
76 root 1.1
77     my $MAX_FPS = 60;
78     my $MIN_FPS = 5; # unused as of yet
79    
80 root 1.125 our $META_SERVER = "http://metaserver.schmorp.de/current.json";
81 root 1.1
82     our $LAST_REFRESH;
83     our $NOW;
84    
85     our $CFG;
86     our $CONN;
87 root 1.120 our $PROFILE; # current profile
88 root 1.1 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
89    
90     our $WANT_REFRESH;
91     our $CAN_REFRESH;
92    
93     our @SDL_MODES;
94     our $WIDTH;
95     our $HEIGHT;
96     our $FULLSCREEN;
97     our $FONTSIZE;
98    
99     our $FONT_PROP;
100     our $FONT_FIXED;
101    
102     our $MAP;
103     our $MAPMAP;
104     our $MAPWIDGET;
105     our $BUTTONBAR;
106     our $LOGVIEW;
107     our $CONSOLE;
108     our $METASERVER;
109     our $LOGIN_BUTTON;
110     our $QUIT_DIALOG;
111 root 1.40 our $HOST_ENTRY;
112 root 1.94 our $FULLSCREEN_ENABLE;
113 root 1.86 our $PICKUP_ENABLE;
114 root 1.67 our $SERVER_INFO;
115 root 1.49
116     our $SETUP_DIALOG;
117     our $SETUP_NOTEBOOK;
118     our $SETUP_SERVER;
119     our $SETUP_KEYBOARD;
120 root 1.1
121 root 1.86 our $PL_NOTEBOOK;
122     our $PL_WINDOW;
123    
124     our $INVENTORY_PAGE;
125     our $STATS_PAGE;
126 root 1.95 our $SKILL_PAGE;
127 root 1.86 our $SPELL_PAGE;
128 root 1.131 our $SPELL_LIST;
129 root 1.86
130     our $HELP_WINDOW;
131 root 1.60 our $MESSAGE_WINDOW;
132 root 1.1 our $FLOORBOX;
133     our $GAUGES;
134     our $STATWIDS;
135    
136     our $SDL_ACTIVE;
137     our %SDL_CB;
138    
139     our $SDL_MIXER;
140     our @SOUNDS; # event => file mapping
141     our %AUDIO_CHUNKS; # audio files
142    
143     our $ALT_ENTER_MESSAGE;
144     our $STATUSBOX;
145     our $DEBUG_STATUS;
146    
147     our $INV;
148     our $INVR;
149 elmex 1.27 our $INV_RIGHT_HB;
150 root 1.1
151 elmex 1.34 our $BIND_EDITOR;
152 elmex 1.77 our $BIND_UPD_CB;
153 elmex 1.24
154 elmex 1.43 our $PICKUP_CFG;
155 elmex 1.38
156 root 1.1 sub status {
157 root 1.104 $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
158 root 1.1 }
159    
160     sub debug {
161     $DEBUG_STATUS->set_text ($_[0]);
162     }
163    
164 root 1.122 sub message {
165     my ($para) = @_;
166    
167     my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
168    
169     $para->{markup} = "<span foreground='#ffffff'>$time</span> $para->{markup}";
170    
171     $LOGVIEW->add_paragraph ($para);
172     $LOGVIEW->scroll_to_bottom;
173     }
174    
175 root 1.60 sub destroy_query_dialog {
176     (delete $_[0]{query_dialog})->destroy
177     if $_[0]{query_dialog};
178     }
179    
180     # server query dialog
181     sub server_query {
182     my ($conn, $flags, $prompt) = @_;
183    
184 root 1.114 $conn->{query_dialog} = my $dialog = new CFPlus::UI::Toplevel
185 root 1.60 x => "center",
186     y => "center",
187     title => "Server Query",
188 root 1.104 child => my $vbox = new CFPlus::UI::VBox,
189 root 1.60 ;
190    
191 root 1.104 my @dialog = my $label = new CFPlus::UI::Label
192 root 1.60 max_w => $::WIDTH * 0.4,
193     ellipsise => 0,
194     text => $prompt;
195    
196     if ($flags & CS_QUERY_YESNO) {
197 root 1.104 push @dialog, my $hbox = new CFPlus::UI::HBox;
198 root 1.60
199 root 1.104 $hbox->add (new CFPlus::UI::Button
200 root 1.60 text => "No",
201     on_activate => sub {
202     $conn->send ("reply n");
203     $dialog->destroy;
204 root 1.74 0
205 root 1.60 }
206     );
207 root 1.104 $hbox->add (new CFPlus::UI::Button
208 root 1.60 text => "Yes",
209     on_activate => sub {
210     $conn->send ("reply y");
211     destroy_query_dialog $conn;
212 root 1.74 0
213 root 1.60 },
214     );
215    
216 root 1.74 $dialog->grab_focus;
217 root 1.60
218     } elsif ($flags & CS_QUERY_SINGLECHAR) {
219 elmex 1.108 $dialog->{tooltip} = "#charcreation_focus";
220 root 1.60
221     if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
222     $MESSAGE_WINDOW->show;
223    
224 root 1.104 unshift @dialog, new CFPlus::UI::Label
225 root 1.60 max_w => $::WIDTH * 0.4,
226     ellipsise => 0,
227 root 1.98 markup => "\nOr use your keyboard and the text entry below:\n";
228 root 1.60
229 root 1.104 unshift @dialog, my $table = new CFPlus::UI::Table;
230 root 1.60
231 root 1.104 $table->add (0, 0, new CFPlus::UI::Button
232 root 1.60 text => "Next Race",
233     on_activate => sub {
234     $conn->send ("reply n");
235     destroy_query_dialog $conn;
236 root 1.74 0
237 root 1.60 },
238     );
239 root 1.104 $table->add (2, 0, new CFPlus::UI::Button
240 root 1.60 text => "Accept",
241     on_activate => sub {
242     $conn->send ("reply d");
243     destroy_query_dialog $conn;
244 root 1.74 0
245 root 1.60 },
246     );
247    
248 root 1.104 unshift @dialog, new CFPlus::UI::Label
249 root 1.60 max_w => $::WIDTH * 0.4,
250     ellipsise => 0,
251 root 1.109 markup => (CFPlus::Pod::section_label ui => "chargen_race"),
252 root 1.60 ;
253    
254     } elsif ($prompt =~ /roll new stats/) {
255     if (my $stat = delete $conn->{stat_change_with}) {
256     $conn->send ("reply $stat");
257     destroy_query_dialog $conn;
258     return;
259     }
260    
261 root 1.86 $STATS_PAGE->show;
262 root 1.60 $MESSAGE_WINDOW->hide;
263    
264 root 1.104 unshift @dialog, new CFPlus::UI::Label
265 root 1.60 max_w => $::WIDTH * 0.4,
266     ellipsise => 0,
267 root 1.98 markup => "\nOr use your keyboard and the text entry below:\n";
268 root 1.60
269 root 1.104 unshift @dialog, my $table = new CFPlus::UI::Table;
270 root 1.60
271     # left: re-roll
272 root 1.104 $table->add (0, 0, new CFPlus::UI::Button
273 root 1.60 text => "Roll Again",
274     on_activate => sub {
275     $conn->send ("reply y");
276     destroy_query_dialog $conn;
277 root 1.74 0
278 root 1.60 },
279     );
280    
281     # center: swap stats
282 root 1.104 my ($sw1, $sw2) = map +(new CFPlus::UI::Selector
283 root 1.98 expand => 1,
284 root 1.60 value => $_,
285     options => [
286 root 1.64 [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
287     [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
288     [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
289     [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
290     [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
291     [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
292     [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
293 root 1.60 ],
294     ), 1 .. 2;
295    
296 root 1.104 $table->add (2, 0, new CFPlus::UI::Button
297 root 1.60 text => "Swap Stats",
298     on_activate => sub {
299     $conn->{stat_change_with} = $sw2->{value};
300     $conn->send ("reply $sw1->{value}");
301     destroy_query_dialog $conn;
302 root 1.74 0
303 root 1.60 },
304     );
305 root 1.104 $table->add (2, 1, new CFPlus::UI::HBox children => [$sw1, $sw2]);
306 root 1.60
307     # right: accept
308 root 1.104 $table->add (4, 0, new CFPlus::UI::Button
309 root 1.60 text => "Accept",
310     on_activate => sub {
311     $conn->send ("reply n");
312 root 1.86 $STATS_PAGE->hide;
313 root 1.60 destroy_query_dialog $conn;
314 root 1.74 0
315 root 1.60 },
316     );
317    
318 root 1.104 unshift @dialog, my $hbox = new CFPlus::UI::HBox;
319 root 1.98 for (
320     [Str => CS_STAT_STR],
321     [Dex => CS_STAT_DEX],
322     [Con => CS_STAT_CON],
323     [Int => CS_STAT_INT],
324     [Wis => CS_STAT_WIS],
325     [Pow => CS_STAT_POW],
326     [Cha => CS_STAT_CHA],
327     ) {
328     my ($name, $id) = @$_;
329 root 1.104 $hbox->add (new CFPlus::UI::Label
330 root 1.98 markup => "$conn->{stat}{$id} <span foreground='yellow'>$name</span>",
331     align => 0,
332     expand => 1,
333     can_events => 1,
334     can_hover => 1,
335 root 1.107 tooltip => "#stat_$name",
336 root 1.98 );
337     }
338    
339 root 1.104 unshift @dialog, new CFPlus::UI::Label
340 root 1.60 max_w => $::WIDTH * 0.4,
341     ellipsise => 0,
342 root 1.109 markup => (CFPlus::Pod::section_label ui => "chargen_stats"),
343 root 1.60 ;
344     }
345    
346 root 1.104 push @dialog, my $entry = new CFPlus::UI::Entry
347 root 1.60 on_changed => sub {
348     $conn->send ("reply $_[1]");
349     destroy_query_dialog $conn;
350 root 1.74 0
351 root 1.60 },
352     ;
353    
354 root 1.74 $entry->grab_focus;
355 root 1.60
356     } else {
357     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
358    
359 root 1.104 push @dialog, my $entry = new CFPlus::UI::Entry
360 root 1.60 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
361     on_activate => sub {
362     $conn->send ("reply $_[1]");
363     destroy_query_dialog $conn;
364 root 1.74 0
365 root 1.60 },
366     ;
367    
368 root 1.74 $entry->grab_focus;
369 root 1.60 }
370    
371     $vbox->add (@dialog);
372     $dialog->show;
373     }
374    
375 root 1.1 sub start_game {
376     status "logging in...";
377    
378 root 1.23 $LOGIN_BUTTON->set_text ("Logout");
379 root 1.49 $SETUP_DIALOG->hide;
380 root 1.23
381 root 1.120 $PROFILE = $CFG->{profile}{default};
382    
383 root 1.1 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
384    
385 root 1.120 my ($host, $port) = split /:/, $PROFILE->{host};
386 root 1.11
387 root 1.124 $MAP = new CFPlus::Map;
388 root 1.1
389     $CONN = eval {
390 root 1.104 new CFPlus::Protocol
391 root 1.1 host => $host,
392     port => $port || 13327,
393 root 1.120 user => $PROFILE->{user},
394     pass => $PROFILE->{password},
395 root 1.1 mapw => $mapsize,
396     maph => $mapsize,
397 root 1.11
398 root 1.121 client => "cfplus $CFPlus::VERSION $] $^O",
399 root 1.118
400 root 1.11 map_widget => $MAPWIDGET,
401     logview => $LOGVIEW,
402     statusbox => $STATUSBOX,
403     map => $MAP,
404     mapmap => $MAPMAP,
405 root 1.60 query => \&server_query,
406 root 1.11
407     sound_play => sub {
408     my ($x, $y, $soundnum, $type) = @_;
409    
410     $SDL_MIXER
411     or return;
412    
413     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
414     or return;
415    
416     $chunk->play;
417     },
418 root 1.1 };
419    
420     if ($CONN) {
421 root 1.104 CFPlus::lowdelay fileno $CONN->{fh};
422 root 1.1
423     status "login successful";
424     } else {
425     status "unable to connect";
426     stop_game();
427     }
428     }
429    
430     sub stop_game {
431 root 1.23 $LOGIN_BUTTON->set_text ("Login");
432 root 1.53 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
433 root 1.49 $SETUP_DIALOG->show;
434 elmex 1.85 $PL_WINDOW->hide;
435 root 1.86 $SPELL_PAGE->clear_spells;
436 root 1.23
437 root 1.1 return unless $CONN;
438    
439     status "connection closed";
440 root 1.23
441 root 1.60 destroy_query_dialog $CONN;
442 root 1.1 $CONN->destroy;
443     $CONN = 0; # false, does not autovivify
444 root 1.76
445     undef $MAP;
446 root 1.1 }
447    
448 root 1.49 sub graphics_setup {
449 root 1.104 my $vbox = new CFPlus::UI::VBox;
450 root 1.30
451 root 1.104 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
452 root 1.1
453 root 1.104 $table->add (0, 0, new CFPlus::UI::Label valign => 0, align => 1, text => "Video Mode");
454     $table->add (1, 0, my $hbox = new CFPlus::UI::HBox);
455 root 1.1
456 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]);
457     $hbox->add (my $mode_label = new CFPlus::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
458 root 1.1
459     $mode_slider->connect (changed => sub {
460     my ($self, $value) = @_;
461    
462     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
463     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
464     });
465     $mode_slider->emit (changed => $mode_slider->{range}[0]);
466    
467     my $row = 1;
468    
469 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fullscreen");
470     $table->add (1, $row++, $FULLSCREEN_ENABLE = new CFPlus::UI::CheckBox
471 root 1.1 state => $CFG->{fullscreen},
472     tooltip => "Bring the client into fullscreen mode.",
473 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
474 root 1.1 );
475    
476 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
477     $table->add (1, $row++, new CFPlus::UI::CheckBox
478 root 1.1 state => $CFG->{fast},
479     tooltip => "Lower the visual quality considerably to speed up rendering.",
480 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
481 root 1.1 );
482    
483 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Scale");
484     $table->add (1, $row++, new CFPlus::UI::Slider
485 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
486     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
487 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
488 root 1.1 );
489    
490 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fog of War");
491     $table->add (1, $row++, new CFPlus::UI::CheckBox
492 root 1.1 state => $CFG->{fow_enable},
493     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
494 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
495 root 1.1 );
496    
497 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "FoW Intensity");
498     $table->add (1, $row++, new CFPlus::UI::Slider
499 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
500     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
501 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
502 root 1.1 );
503    
504 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "FoW Smooth");
505     $table->add (1, $row++, new CFPlus::UI::CheckBox
506 root 1.1 state => $CFG->{fow_smooth},
507     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
508 root 1.18 on_changed => sub {
509 root 1.1 my ($self, $value) = @_;
510     $CFG->{fow_smooth} = $value;
511 root 1.104 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFPlus::OpenGL::GL_VERSION < 1.2;
512 root 1.74 0
513 root 1.1 }
514     );
515    
516 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
517     $table->add (1, $row++, new CFPlus::UI::Slider
518 root 1.1 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
519     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
520 root 1.74 on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
521 root 1.1 );
522    
523 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Message Fontsize");
524     $table->add (1, $row++, new CFPlus::UI::Slider
525 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
526     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
527 root 1.74 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
528 root 1.1 );
529    
530 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
531     $table->add (1, $row++, new CFPlus::UI::Slider
532 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
533     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
534 root 1.18 on_changed => sub {
535 root 1.1 $CFG->{gauge_fontsize} = $_[1];
536     &set_gauge_window_fontsize;
537 root 1.74 0
538 root 1.1 }
539     );
540    
541 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge size");
542     $table->add (1, $row++, new CFPlus::UI::Slider
543 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
544     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
545     on_changed => sub {
546 root 1.1 $CFG->{gauge_size} = $_[1];
547     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
548 root 1.74 0
549 root 1.1 }
550     );
551    
552 root 1.104 $table->add (1, $row++, new CFPlus::UI::Button
553 root 1.1 expand => 1, align => 0, text => "Apply",
554     tooltip => "Apply the video settings",
555 root 1.18 on_activate => sub {
556 root 1.1 video_shutdown ();
557     video_init ();
558 root 1.74 0
559 root 1.1 }
560     );
561    
562 root 1.49 $vbox
563     }
564    
565     sub audio_setup {
566 root 1.104 my $vbox = new CFPlus::UI::VBox;
567 root 1.49
568 root 1.104 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
569 root 1.49
570     my $row = 0;
571    
572 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable");
573     $table->add (1, $row++, new CFPlus::UI::CheckBox
574 root 1.1 state => $CFG->{audio_enable},
575     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.",
576 root 1.74 on_changed => sub { $CFG->{audio_enable} = $_[1]; 0 }
577 root 1.1 );
578 root 1.104 # $table->add (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Effects Volume");
579     # $table->add (1, 8, new CFPlus::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
580 root 1.1 # $CFG->{effects_volume} = $_[1];
581     # });
582 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music");
583     $table->add (1, $row++, my $hbox = new CFPlus::UI::HBox);
584     $hbox->add (new CFPlus::UI::CheckBox
585 root 1.1 expand => 1, state => $CFG->{bgm_enable},
586     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
587 root 1.74 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 }
588 root 1.1 );
589 root 1.104 $hbox->add (new CFPlus::UI::Slider
590 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
591     tooltip => "The volume of the background music. Changes are instant.",
592 root 1.104 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFPlus::MixMusic::volume $_[1] * 128; 0 }
593 root 1.1 );
594    
595 root 1.104 $table->add (1, $row++, new CFPlus::UI::Button
596 root 1.1 expand => 1, align => 0, text => "Apply",
597     tooltip => "Apply the audio settings",
598 root 1.18 on_activate => sub {
599 root 1.1 audio_shutdown ();
600     audio_init ();
601 root 1.74 0
602 root 1.1 }
603     );
604    
605 root 1.49 $vbox
606 root 1.1 }
607    
608     sub set_gauge_window_fontsize {
609     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
610     $_->set_fontsize ($::CFG->{gauge_fontsize});
611     }
612     }
613    
614     sub make_gauge_window {
615     my $gh = int $HEIGHT * $CFG->{gauge_size};
616    
617 root 1.104 my $win = new CFPlus::UI::Frame (
618 root 1.30 force_x => 0,
619     force_y => "max",
620     force_w => $WIDTH,
621     force_h => $gh,
622 root 1.1 );
623    
624 root 1.104 $win->add (my $hbox = new CFPlus::UI::HBox
625 root 1.1 children => [
626 root 1.104 (new CFPlus::UI::HBox expand => 1),
627     (new CFPlus::UI::VBox children => [
628     (new CFPlus::UI::Empty expand => 1),
629     (new CFPlus::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFPlus::UI::Table)),
630 root 1.1 ]),
631 root 1.104 (my $vbox = new CFPlus::UI::VBox),
632 root 1.1 ],
633     );
634    
635 root 1.104 $vbox->add (new CFPlus::UI::HBox
636 root 1.1 expand => 1,
637     children => [
638 root 1.104 (new CFPlus::UI::Empty expand => 1),
639     (my $hb = new CFPlus::UI::HBox),
640 root 1.1 ],
641     );
642    
643 root 1.109 $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health");
644     $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana");
645     $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace");
646     $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food");
647    
648     $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
649     $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
650 root 1.1
651     $GAUGES = {
652     exp => $exp, win => $win, range => $rng,
653     food => $fg, mana => $mg, hp => $hg, grace => $gg
654     };
655    
656     &set_gauge_window_fontsize;
657    
658     $win
659     }
660    
661 root 1.65 sub debug_setup {
662 root 1.104 my $table = new CFPlus::UI::Table;
663 root 1.65
664 root 1.104 $table->add (0, 0, new CFPlus::UI::Label text => "Widget Borders");
665     $table->add (1, 0, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
666     $table->add (0, 1, new CFPlus::UI::Label text => "Tooltip Widget Info");
667     $table->add (1, 1, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
668     $table->add (0, 2, new CFPlus::UI::Label text => "Show FPS");
669     $table->add (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
670     $table->add (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips");
671     $table->add (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
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     ["Readables", 2, 2,
1196 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1197     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1198     ["Normal Books/Scrolls" => PICKUP_READABLES],
1199 root 1.51 ],
1200     ["Misc", 2, 7,
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.51 ],
1211 elmex 1.66 ["Weight/Value ratio", 2, 17]
1212 elmex 1.43 )
1213     {
1214 root 1.51 my ($title, $x, $y, @bits) = @$_;
1215 root 1.104 $table->add ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1216 root 1.51
1217     for (@bits) {
1218     ++$y;
1219    
1220 elmex 1.43 my $mask = $_->[1];
1221 root 1.104 $table->add ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1222     $table->add ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1223 elmex 1.83 state => $::CFG->{pickup} & $mask,
1224 elmex 1.43 on_changed => sub {
1225     my ($box, $value) = @_;
1226 root 1.63
1227 elmex 1.43 if ($value) {
1228 elmex 1.45 $::CFG->{pickup} |= $mask;
1229 elmex 1.43 } else {
1230 root 1.63 $::CFG->{pickup} &= ~$mask;
1231 elmex 1.43 }
1232 root 1.63
1233     $::CONN->send_command ("pickup $::CFG->{pickup}")
1234 elmex 1.45 if defined $::CONN;
1235 root 1.74
1236     0
1237 elmex 1.43 });
1238 root 1.86
1239     ${$_->[2]} = $checkbox if $_->[2];
1240 elmex 1.43 }
1241     }
1242    
1243 root 1.104 $table->add (2, 18, new CFPlus::UI::ValSlider
1244 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1245     template => ">= 99",
1246 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1247     on_changed => sub {
1248     my ($slider, $value) = @_;
1249    
1250 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1251 elmex 1.66 $::CFG->{pickup} |= int $value
1252     if $value;
1253     1;
1254     });
1255 elmex 1.83
1256 root 1.104 $table->add (3, 18, new CFPlus::UI::Button
1257 elmex 1.66 text => "set",
1258     on_activate => sub {
1259     $::CONN->send_command ("pickup $::CFG->{pickup}")
1260     if defined $::CONN;
1261 root 1.74 0
1262 elmex 1.66 });
1263    
1264 root 1.51 $table
1265 elmex 1.43 }
1266    
1267 root 1.102 my %SORT_ORDER = (
1268     type => undef,
1269 root 1.130 mtime => sub {
1270     my $NOW = time;
1271     sort {
1272     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1273     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1274    
1275     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1276     or $btime <=> $atime
1277     or $a->{type} <=> $b->{type}
1278     } @_
1279     },
1280 root 1.102 weight => sub { sort {
1281     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1282     or $a->{type} <=> $b->{type}
1283     } @_ },
1284     );
1285    
1286 elmex 1.85 sub inventory_widget {
1287 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1288 root 1.1
1289 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1290     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1291 root 1.99
1292 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1293 root 1.99
1294     use sort 'stable';
1295    
1296 root 1.104 $hb1->add (new CFPlus::UI::Selector
1297 root 1.102 value => $::CFG->{inv_sort},
1298 root 1.99 options => [
1299 root 1.102 [type => "Type/Name"],
1300     [mtime => "Recent/Normal/Locked"],
1301     [weight => "Weight/Type"],
1302 root 1.99 ],
1303     on_changed => sub {
1304 root 1.102 $::CFG->{inv_sort} = $_[1];
1305     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1306 root 1.99 },
1307     );
1308 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1309 root 1.99 #TODO# update to weigh/maxweight
1310 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1311 root 1.99
1312 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1313     $sw1->add ($INV = new CFPlus::UI::Inventory);
1314 root 1.127 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1315 root 1.1
1316 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1317 elmex 1.17
1318 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1319 elmex 1.14
1320 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1321     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1322 root 1.1
1323 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1324 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1325 elmex 1.27
1326 elmex 1.85 $hb
1327 root 1.1 }
1328    
1329 root 1.86 sub toggle_player_page {
1330     my ($widget) = @_;
1331    
1332     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1333     $PL_WINDOW->hide;
1334     } else {
1335     $PL_NOTEBOOK->set_current_page ($widget);
1336     $PL_WINDOW->show;
1337     }
1338     }
1339    
1340 elmex 1.85 sub player_window {
1341 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1342 elmex 1.85 x => "center",
1343     y => "center",
1344     force_w => $WIDTH * 9/10,
1345     force_h => $HEIGHT * 9/10,
1346     title => "Player",
1347 elmex 1.90 name => "playerbook",
1348 elmex 1.85 has_close_button => 1
1349     ;
1350    
1351     my $ntb =
1352     $PL_NOTEBOOK =
1353 root 1.104 new CFPlus::UI::Notebook expand => 1, debug => 1;
1354 root 1.86
1355 elmex 1.85 $ntb->add (
1356 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1357 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1358     );
1359     $ntb->add (
1360 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1361 elmex 1.92 "Shows all your Skills."
1362 elmex 1.85 );
1363 elmex 1.97
1364 root 1.131 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1365     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1366 elmex 1.85 $ntb->add (
1367 elmex 1.97 "Spellbook (F4)" => $spellsw,
1368 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1369 elmex 1.85 );
1370     $ntb->add (
1371 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1372 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1373     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1374 elmex 1.85 );
1375    
1376 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1377 root 1.86
1378 elmex 1.85 $plwin->add ($ntb);
1379     $plwin
1380 elmex 1.38 }
1381    
1382 elmex 1.77 sub update_bindings {
1383     $BIND_UPD_CB->() if $BIND_UPD_CB;
1384     }
1385    
1386 root 1.49 sub keyboard_setup {
1387 root 1.104 my $binding_list = new CFPlus::UI::VBox;
1388 elmex 1.24
1389 elmex 1.34 my $refresh;
1390 elmex 1.77 $refresh = $BIND_UPD_CB = sub {
1391 elmex 1.24 $binding_list->clear ();
1392    
1393 root 1.120 return unless $PROFILE;
1394    
1395     for my $mod (keys %{$PROFILE->{bindings}}) {
1396     for my $sym (keys %{$PROFILE->{bindings}{$mod}}) {
1397     my $cmds = $PROFILE->{bindings}{$mod}{$sym};
1398 elmex 1.24 next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1399    
1400     my $lbl = join "; ", @$cmds;
1401 root 1.104 my $nam = CFPlus::BindingEditor::keycombo_to_name ($mod, $sym);
1402     $binding_list->add (my $hb = new CFPlus::UI::HBox);
1403     $hb->add (new CFPlus::UI::Button
1404 elmex 1.25 text => "delete",
1405 elmex 1.34 tooltip => "Deletes the binding",
1406 elmex 1.24 on_activate => sub {
1407     $binding_list->remove ($hb);
1408 root 1.120 delete $PROFILE->{bindings}{$mod}{$sym};
1409 root 1.74 0
1410 elmex 1.24 });
1411 elmex 1.34
1412 root 1.104 $hb->add (new CFPlus::UI::Button
1413 elmex 1.34 text => "edit",
1414     tooltip => "Edits the binding",
1415     on_activate => sub {
1416     $::BIND_EDITOR->set_binding (
1417 root 1.120 $mod, $sym, $PROFILE->{bindings}{$mod}{$sym},
1418 elmex 1.34 sub {
1419     my ($nmod, $nsym, $ncmds) = @_;
1420 elmex 1.77 $::BIND_EDITOR->cfg_unbind ($mod, $sym);
1421     $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds);
1422 elmex 1.34 $refresh->();
1423 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1424     $SETUP_DIALOG->show;
1425 elmex 1.34 },
1426     sub {
1427 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1428     $SETUP_DIALOG->show;
1429 elmex 1.34 });
1430     $::BIND_EDITOR->show;
1431 root 1.49 $SETUP_DIALOG->hide;
1432 root 1.74 0
1433 elmex 1.34 });
1434    
1435 root 1.104 $hb->add (new CFPlus::UI::Label text => "(Key: $nam)");
1436     $hb->add (new CFPlus::UI::Label text => $lbl, expand => 1);
1437 elmex 1.24 }
1438     }
1439     };
1440    
1441 root 1.104 my $vb = new CFPlus::UI::VBox;
1442 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1443     label => "Options",
1444     child => (my $hb = new CFPlus::UI::HBox),
1445     );
1446 root 1.104 $hb->add (new CFPlus::UI::Label text => "only shift-up stops fire");
1447     $hb->add (new CFPlus::UI::CheckBox
1448 elmex 1.71 expand => 1,
1449     state => $CFG->{shift_fire_stop},
1450     tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift",
1451     on_changed => sub {
1452     my ($cbox, $value) = @_;
1453     $CFG->{shift_fire_stop} = $value;
1454 root 1.74 0
1455 elmex 1.71 });
1456    
1457 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1458     label => "Bindings",
1459     child => $binding_list);
1460 root 1.104 $vb->add (my $hb = new CFPlus::UI::HBox);
1461 root 1.49
1462 root 1.104 $hb->add (new CFPlus::UI::Button
1463 elmex 1.34 text => "record new",
1464 elmex 1.35 expand => 1,
1465 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1466     on_activate => sub {
1467     $::BIND_EDITOR->set_binding (undef, undef, [],
1468     sub {
1469     my ($mod, $sym, $cmds) = @_;
1470 elmex 1.77 $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds);
1471 elmex 1.34 $refresh->();
1472 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1473     $SETUP_DIALOG->show;
1474 elmex 1.34 },
1475     sub {
1476 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1477     $SETUP_DIALOG->show;
1478 root 1.53 },
1479     );
1480 root 1.49 $SETUP_DIALOG->hide;
1481 elmex 1.34 $::BIND_EDITOR->show;
1482 root 1.74 0
1483 elmex 1.34 },
1484     );
1485 root 1.49
1486 root 1.104 $hb->add (new CFPlus::UI::Button
1487 elmex 1.35 text => "close",
1488     tooltip => "Closes the binding window",
1489     expand => 1,
1490     on_activate => sub {
1491 root 1.49 $SETUP_DIALOG->hide;
1492 root 1.74 0
1493 elmex 1.35 }
1494     );
1495    
1496 elmex 1.24 $refresh->();
1497 root 1.49
1498     $vb
1499 elmex 1.24 }
1500    
1501 root 1.64 sub help_window {
1502 root 1.114 my $win = new CFPlus::UI::Toplevel
1503 root 1.41 x => 'center',
1504     y => 'center',
1505 root 1.119 z => 4,
1506 root 1.41 name => 'doc_browser',
1507     force_w => int $WIDTH * 7/8,
1508     force_h => int $HEIGHT * 7/8,
1509 root 1.87 title => "Help Browser",
1510     has_close_button => 1;
1511 root 1.1
1512 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1513 root 1.1
1514 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1515     label => "Navigation",
1516     child => (my $buttons = new CFPlus::UI::HBox),
1517     );
1518 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1519 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1520 root 1.1
1521 root 1.111 my @history;
1522     my @future;
1523 root 1.112 my $curnode;
1524 root 1.111
1525     my $load_node; $load_node = sub {
1526 root 1.115 my ($node, $para) = @_;
1527 root 1.111
1528     $buttons->clear;
1529    
1530 root 1.116 $buttons->add (new CFPlus::UI::Button
1531     text => "⇤",
1532     tooltip => "back to the starting page",
1533     on_activate => sub {
1534     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1535     unshift @future, @history;
1536     @history = ();
1537     $load_node->(@{shift @future});
1538     },
1539     );
1540    
1541 root 1.111 if (@history) {
1542 root 1.112 $buttons->add (new CFPlus::UI::Button
1543 root 1.113 text => "⋘",
1544 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1545 root 1.112 on_activate => sub {
1546 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1547     $load_node->(@{pop @history});
1548 root 1.113 },
1549     );
1550 root 1.112 }
1551 root 1.111
1552 root 1.112 if (@future) {
1553     $buttons->add (new CFPlus::UI::Button
1554 root 1.113 text => "â‹™",
1555 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1556 root 1.112 on_activate => sub {
1557 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1558     $load_node->(@{shift @future});
1559 root 1.113 },
1560     );
1561 root 1.111 }
1562 root 1.112
1563 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1564    
1565     my @path = CFPlus::Pod::full_path_of $node;
1566     pop @path; # drop current node
1567    
1568     for my $node (@path) {
1569     $buttons->add (new CFPlus::UI::Button
1570     text => $node->{kw}[0],
1571     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1572     on_activate => sub {
1573 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1574 root 1.113 $load_node->($node);
1575     },
1576     );
1577     $buttons->add (new CFPlus::UI::Label text => "/");
1578     }
1579    
1580 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1581 root 1.113
1582 root 1.112 $curnode = $node;
1583    
1584 root 1.111 $viewer->clear;
1585 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1586 root 1.115 $viewer->scroll_to ($para);
1587 root 1.111 };
1588    
1589     $load_node->(CFPlus::Pod::find pod => "mainpage");
1590    
1591 root 1.113 $CFPlus::Pod::goto_document = sub {
1592 root 1.111 my (@path) = @_;
1593    
1594 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1595 root 1.111
1596 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1597 root 1.111 $win->show;
1598     };
1599 root 1.1
1600     $win
1601     }
1602    
1603 root 1.119 sub open_string_query {
1604     my ($title, $cb, $txt, $tooltip) = @_;
1605     my $dialog = new CFPlus::UI::Toplevel
1606     x => "center",
1607     y => "center",
1608     z => 50,
1609     force_w => $WIDTH * 4/5,
1610     title => $title;
1611    
1612     $dialog->add (
1613     my $e = new CFPlus::UI::Entry
1614     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1615     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1616     tooltip => $tooltip
1617     );
1618    
1619     $e->grab_focus;
1620     $e->set_text ($txt) if $txt;
1621     $dialog->show;
1622     }
1623    
1624     sub open_quit_dialog {
1625     unless ($QUIT_DIALOG) {
1626     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1627     x => "center",
1628     y => "center",
1629     z => 50,
1630     title => "Really Quit?",
1631     on_key_down => sub {
1632     my ($dialog, $ev) = @_;
1633     $ev->{sym} == 27 and $dialog->hide;
1634     }
1635     ;
1636    
1637     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1638    
1639     $vb->add (new CFPlus::UI::Label
1640     text => "You should find a savebed and apply it first!",
1641     max_w => $WIDTH * 0.25,
1642     ellipsize => 0,
1643     );
1644     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1645     $hb->add (new CFPlus::UI::Button
1646     text => "Ok",
1647     expand => 1,
1648     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1649     );
1650     $hb->add (new CFPlus::UI::Button
1651     text => "Quit anyway",
1652     expand => 1,
1653     on_activate => sub { exit },
1654     );
1655     }
1656    
1657     $QUIT_DIALOG->show;
1658     $QUIT_DIALOG->grab_focus;
1659     }
1660    
1661     sub show_tip_of_the_day {
1662     # find all tips
1663     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1664    
1665     my $todindex = $CFPlus::DB_STATE->get ("tip_of_the_day");
1666     $todindex = 0 if $todindex >= @tod;
1667     $CFPlus::DB_STATE->put (tip_of_the_day => $todindex + 1);
1668    
1669     # create dialog
1670     my $dialog;
1671    
1672     my $close = sub {
1673     $dialog->destroy;
1674     };
1675    
1676     $dialog = new CFPlus::UI::Toplevel
1677     x => "center",
1678     y => "center",
1679     z => 3,
1680     name => 'tip_of_the_day',
1681     force_w => int $WIDTH * 4/9,
1682     force_h => int $WIDTH * 2/9,
1683     title => "Tip of the day #" . (1 + $todindex),
1684     child => my $vbox = new CFPlus::UI::VBox,
1685     has_close_button => 1,
1686     on_delete => $close,
1687     ;
1688    
1689     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1690     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1691     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1692    
1693     $vbox->add (my $table = new CFPlus::UI::Table);
1694    
1695     $table->add (0, 0, new CFPlus::UI::Button
1696     text => "Close",
1697     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>.",
1698     on_activate => $close,
1699     );
1700    
1701     $table->add (2, 0, new CFPlus::UI::Button
1702     text => "Next",
1703     tooltip => "Show the next <b>Tip of the day</b>.",
1704     on_activate => sub {
1705     $close->();
1706     &show_tip_of_the_day;
1707     },
1708     );
1709    
1710     $dialog->show;
1711     }
1712    
1713 root 1.1 sub sdl_init {
1714 root 1.104 CFPlus::SDL_Init
1715 root 1.1 and die "SDL::Init failed!\n";
1716     }
1717    
1718     sub video_init {
1719     sdl_init;
1720    
1721     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1722    
1723     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1724    
1725     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1726     $FULLSCREEN = $CFG->{fullscreen};
1727     $FAST = $CFG->{fast};
1728    
1729 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1730     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1731 root 1.1
1732     $SDL_ACTIVE = 1;
1733     $LAST_REFRESH = time - 0.01;
1734    
1735 root 1.104 CFPlus::OpenGL::init;
1736 root 1.1
1737     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1738    
1739 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1740 root 1.1
1741     #############################################################################
1742    
1743     if ($DEBUG_STATUS) {
1744 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1745 root 1.1 } else {
1746     # create the widgets
1747    
1748 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1749 root 1.30 padding => 0,
1750     z => 100,
1751     force_x => "max",
1752     force_y => 0;
1753 root 1.1 $DEBUG_STATUS->show;
1754 elmex 1.34
1755 root 1.104 $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0);
1756 elmex 1.34
1757 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1758 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1759 root 1.1
1760 root 1.104 (new CFPlus::UI::Frame
1761 root 1.1 bg => [0, 0, 0, 0.4],
1762 root 1.30 force_x => 0,
1763     force_y => "max",
1764 root 1.1 child => $STATUSBOX,
1765     )->show;
1766    
1767 root 1.114 CFPlus::UI::Toplevel->new (
1768 root 1.47 title => "Map",
1769 root 1.42 name => "mapmap",
1770 root 1.30 x => 0,
1771     y => $FONTSIZE + 8,
1772 root 1.1 border_bg => [1, 1, 1, 192/255],
1773     bg => [1, 1, 1, 0],
1774 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1775 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1776     ),
1777     )->show;
1778    
1779 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1780 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1781     my ($mapwidget, $preset) = @_;
1782    
1783     if ($CONSOLE) {
1784     $CONSOLE->{input}->{auto_activated} = 1;
1785 root 1.74 $CONSOLE->{input}->grab_focus;
1786 root 1.1
1787     if ($preset && $CONSOLE->{input}->get_text eq '') {
1788     $CONSOLE->{input}->set_text ($preset);
1789     }
1790     }
1791     });
1792     $MAPWIDGET->show;
1793 root 1.74 $MAPWIDGET->grab_focus;
1794 root 1.1
1795 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1796 root 1.1 expand => 1,
1797     font => $FONT_FIXED,
1798     fontsize => $::CFG->{log_fontsize},
1799 root 1.61 indent => -4,
1800 root 1.1 can_hover => 1,
1801     can_events => 1,
1802     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1803     ;
1804    
1805 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1806 root 1.49 title => "Setup",
1807     name => "setup_dialog",
1808     x => 'center',
1809     y => 'center',
1810 root 1.53 z => 2,
1811 root 1.49 force_w => $::WIDTH * 0.6,
1812     force_h => $::HEIGHT * 0.6,
1813 root 1.74 has_close_button => 1,
1814 root 1.49 ;
1815    
1816 elmex 1.81 $METASERVER = metaserver_dialog;
1817    
1818 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1819     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1820 root 1.49
1821     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1822     "Configure the server to play on, your username, password and other server-related options.");
1823     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1824 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1825 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1826     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1827     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1828     "Configure the use of audio, sound effects and background music.");
1829     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1830 root 1.75 "Lets you define, edit and delete key bindings."
1831     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1832 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1833 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1834 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1835     . "binding editor closes");
1836 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1837 root 1.75 "Some debuggin' options. Do not ask.");
1838 root 1.49
1839 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1840 root 1.1
1841 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1842 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1843    
1844 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1845 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1846    
1847     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
1848    
1849 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1850 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1851 root 1.1
1852 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1853 root 1.1 text => "Save Config",
1854     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1855 root 1.18 on_activate => sub {
1856 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1857     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1858 root 1.1 status "Configuration Saved";
1859 root 1.74 0
1860 root 1.1 },
1861     );
1862    
1863 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1864 root 1.1 tooltip => "View Documentation");
1865    
1866 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1867 root 1.18 text => "Quit",
1868     tooltip => "Terminates the program",
1869     on_activate => sub {
1870 root 1.1 if ($CONN) {
1871     open_quit_dialog;
1872     } else {
1873     exit;
1874     }
1875 root 1.74 0
1876 root 1.1 },
1877     );
1878    
1879     $BUTTONBAR->show;
1880 root 1.49 $SETUP_DIALOG->show;
1881     }
1882 root 1.1
1883 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1884 root 1.1 }
1885    
1886     sub video_shutdown {
1887 root 1.104 CFPlus::OpenGL::shutdown;
1888 root 1.73
1889 root 1.1 undef $SDL_ACTIVE;
1890     }
1891    
1892     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1893     my $bgmusic;#TODO#hack#d#
1894    
1895     sub audio_channel_finished {
1896     my ($channel) = @_;
1897    
1898     #warn "channel $channel finished\n";#d#
1899     }
1900    
1901     sub audio_music_finished {
1902     return unless $CFG->{bgm_enable};
1903    
1904     # TODO: hack, do play loop and mood music
1905 root 1.104 $bgmusic = new_from_file CFPlus::MixMusic CFPlus::find_rcfile "music/$bgmusic[0]";
1906 root 1.1 $bgmusic->play (0);
1907    
1908     push @bgmusic, shift @bgmusic;
1909     }
1910    
1911     sub audio_init {
1912     if ($CFG->{audio_enable}) {
1913 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1914     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1915 root 1.1
1916     unless ($SDL_MIXER) {
1917     status "Unable to open sound device: there will be no sound";
1918     return;
1919     }
1920    
1921 root 1.104 CFPlus::Mix_AllocateChannels 8;
1922     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1923 root 1.1
1924     audio_music_finished;
1925    
1926 root 1.129 local $_;
1927 root 1.1 while (<$fh>) {
1928     next if /^\s*#/;
1929     next if /^\s*$/;
1930    
1931     my ($file, $volume, $event) = split /\s+/, $_, 3;
1932    
1933     push @SOUNDS, "$volume,$file";
1934    
1935 root 1.129 $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1936     my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
1937     $chunk->volume ($volume * 128 / 100);
1938     $chunk
1939     };
1940 root 1.1 }
1941     } else {
1942     status "unable to open sound config: $!";
1943     }
1944     }
1945     }
1946    
1947     sub audio_shutdown {
1948 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1949 root 1.1 undef $SDL_MIXER;
1950     @SOUNDS = ();
1951     %AUDIO_CHUNKS = ();
1952     }
1953    
1954     my %animate_object;
1955     my $animate_timer;
1956    
1957     my $fps = 9;
1958    
1959     my %demo;#d#
1960    
1961     sub force_refresh {
1962     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1963 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1964 root 1.1
1965 root 1.104 $CFPlus::UI::ROOT->draw;
1966 root 1.1
1967     $WANT_REFRESH = 0;
1968     $CAN_REFRESH = 0;
1969     $LAST_REFRESH = $NOW;
1970    
1971 root 1.104 CFPlus::SDL_GL_SwapBuffers;
1972 root 1.1 }
1973    
1974 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1975 root 1.1 $NOW = time;
1976    
1977     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1978 root 1.104 for CFPlus::SDL_PollEvent;
1979 root 1.1
1980     if (%animate_object) {
1981     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1982     $WANT_REFRESH++;
1983     }
1984    
1985     if ($WANT_REFRESH) {
1986     force_refresh;
1987     } else {
1988     $CAN_REFRESH = 1;
1989     }
1990     });
1991    
1992     sub animation_start {
1993     my ($widget) = @_;
1994     $animate_object{$widget} = $widget;
1995     }
1996    
1997     sub animation_stop {
1998     my ($widget) = @_;
1999     delete $animate_object{$widget};
2000     }
2001    
2002     # check once/second for faces that need to be prefetched
2003     # this should, of course, only run on demand, but
2004     # SDL forces worse things on us....
2005    
2006     Event->timer (after => 1, interval => 0.25, cb => sub {
2007     $CONN->face_prefetch
2008     if $CONN;
2009     });
2010    
2011     %SDL_CB = (
2012 root 1.104 CFPlus::SDL_QUIT => sub {
2013 root 1.1 Event::unloop -1;
2014     },
2015 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2016 root 1.1 },
2017 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2018     CFPlus::UI::full_refresh;
2019 root 1.1 },
2020 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2021 root 1.1 # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
2022     },
2023 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2024     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2025 root 1.1 # alt-enter
2026 root 1.94 $FULLSCREEN_ENABLE->toggle;
2027 root 1.1 video_shutdown;
2028     video_init;
2029     } else {
2030 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2031 root 1.1 }
2032     },
2033 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2034     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2035     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2036     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2037     CFPlus::SDL_USEREVENT => sub {
2038 root 1.1 if ($_[0]{code} == 1) {
2039     audio_channel_finished $_[0]{data1};
2040     } elsif ($_[0]{code} == 0) {
2041     audio_music_finished;
2042     }
2043     },
2044     );
2045    
2046     #############################################################################
2047    
2048     $SIG{INT} = $SIG{TERM} = sub { exit };
2049    
2050     {
2051 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2052     CFPlus::UI::set_layout ($::CFG->{layout});
2053 root 1.1
2054     my %DEF_CFG = (
2055 root 1.75 sdl_mode => 0,
2056     width => 640,
2057     height => 480,
2058     fullscreen => 0,
2059     fast => 0,
2060     map_scale => 1,
2061     fow_enable => 1,
2062     fow_intensity => 0.45,
2063     fow_smooth => 0,
2064     gui_fontsize => 1,
2065     log_fontsize => 0.7,
2066     gauge_fontsize => 1,
2067     gauge_size => 0.35,
2068     stat_fontsize => 0.7,
2069     mapsize => 100,
2070 root 1.126 say_command => 'chat',
2071 root 1.75 audio_enable => 1,
2072     bgm_enable => 1,
2073     bgm_volume => 0.25,
2074     face_prefetch => 0,
2075     output_sync => 1,
2076     output_count => 1,
2077     pickup => 0,
2078 root 1.102 inv_sort => "mtime",
2079 root 1.75 default => "profile", # default profile
2080 root 1.119 show_tips => 1,
2081 root 1.1 );
2082 root 1.75
2083 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2084     $CFG->{$k} = $v unless exists $CFG->{$k};
2085     }
2086    
2087 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2088    
2089 root 1.1 sdl_init;
2090    
2091     @SDL_MODES = reverse
2092     grep $_->[0] >= 640 && $_->[1] >= 480,
2093 root 1.104 CFPlus::SDL_ListModes;
2094 root 1.1
2095 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2096 root 1.1
2097     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2098    
2099     {
2100 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2101 root 1.1 DejaVuSans.ttf
2102     DejaVuSansMono.ttf
2103     DejaVuSans-Bold.ttf
2104     DejaVuSansMono-Bold.ttf
2105     DejaVuSans-Oblique.ttf
2106     DejaVuSansMono-Oblique.ttf
2107     DejaVuSans-BoldOblique.ttf
2108     DejaVuSansMono-BoldOblique.ttf
2109     );
2110    
2111 root 1.104 CFPlus::add_font $_ for @fonts;
2112 root 1.1
2113 root 1.104 CFPlus::pango_init;
2114 root 1.1
2115 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2116     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2117 root 1.1
2118     $FONT_PROP->make_default;
2119     }
2120    
2121     # compare mono (ft) vs. rgba (cairo)
2122     # ft - 1.8s, cairo 3s, even in alpha-only mode
2123     # for my $rgba (0..1) {
2124     # my $t1 = Time::HiRes::time;
2125     # for (1..1000) {
2126 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2127 root 1.1 # $layout->set_text ("hallo" x 100);
2128     # $layout->render;
2129     # }
2130     # my $t2 = Time::HiRes::time;
2131     # warn $t2-$t1;
2132     # }
2133    
2134 root 1.122 $startup_done->();
2135    
2136 root 1.1 video_init;
2137     audio_init;
2138     }
2139    
2140 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2141    
2142 root 1.1 Event::loop;
2143 root 1.104 #CFPlus::SDL_Quit;
2144     #CFPlus::_exit 0;
2145 root 1.1
2146 root 1.104 END { CFPlus::SDL_Quit }
2147 root 1.1
2148     =head1 NAME
2149    
2150 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2151 root 1.1
2152     =head1 SYNOPSIS
2153    
2154     Just run it - no commandline arguments are supported.
2155    
2156     =head1 USAGE
2157    
2158 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2159 root 1.1 fullscreen and interactively.
2160    
2161 root 1.39 =head1 DEBUGGING
2162    
2163    
2164     CFPLUS_DEBUG - environment variable
2165    
2166     1 draw borders around widgets
2167     2 add low-level widget info to tooltips
2168     4 show fps
2169     8 suppress tooltips
2170    
2171 root 1.1 =head1 AUTHOR
2172    
2173     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2174    
2175    
2176