ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.138
Committed: Sat Dec 9 21:26:46 2006 UTC (17 years, 5 months ago) by root
Branch: MAIN
Changes since 1.137: +0 -1 lines
Log Message:
- do no longer depend on Scalar::Util for weaken
- use a different, cleaner, more correct and less efficient event propagation mechanism
- this fixes the segfault bug in map $_.

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