ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.188
Committed: Sun Jul 29 03:58:26 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.187: +5 -2 lines
Log Message:
much improved sdl_mixer support

- abstracted rwops
- enabled (experimental?) Mix_LoadMUS_RW
- abstracted channels
- implemented effects api
- partial rewrite on perl side only (no need
  to store resources as separate data files anymore).
- catch more bugs w.r.t. textures.

File Contents

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