ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.69
Committed: Mon Sep 1 15:08:15 2008 UTC (15 years, 9 months ago) by root
Branch: MAIN
Changes since 1.68: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.7 if ($ENV{DELIANTRA_CORO_DEBUG}) {
4     eval '
5     use Coro;
6     use Coro::EV;
7     use Coro::Debug;
8     our $debug = new_unix_server Coro::Debug "/tmp/dc";
9     ';
10     }
11    
12 root 1.1 # do splash-screen thingy on win32
13     my $startup_done = sub { };
14     BEGIN {
15     if (%PAR::LibCache && $^O eq "MSWin32") {
16     while (my ($filename, $zip) = each %PAR::LibCache) {
17     $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp");
18     }
19    
20     require Win32::GUI::SplashScreen;
21    
22     Win32::GUI::SplashScreen::Show (
23     -file => "$ENV{PAR_TEMP}/SPLASH.bmp",
24     );
25    
26     $startup_done = sub {
27     Win32::GUI::SplashScreen::Done (1);
28     };
29     }
30     }
31    
32     use strict;
33     use utf8;
34    
35     use Carp 'verbose';
36    
37     # do things only needed for single-binary version (par)
38     BEGIN {
39     if (%PAR::LibCache) {
40     @INC = grep ref, @INC; # weed out all paths except pars loader refs
41    
42     my $root = $ENV{PAR_TEMP};
43    
44     while (my ($filename, $zip) = each %PAR::LibCache) {
45     for ($zip->memberNames) {
46     next unless /^root\/(.*)/;
47     $zip->extractMember ($_, "$root/$1")
48     unless -e "$root/$1";
49     }
50     }
51    
52     if ($^O eq "MSWin32") {
53     # pango is relocatable on win32
54 root 1.60 } elsif (-e "$root/pangoversion") {
55 root 1.1 open my $fh, "<:perlio", "$root/pangoversion"
56     or die "pangoversion: $!";
57     my $PANGO = <$fh>;
58     # unix, need to patch pango rc file
59     open my $fh, "<:perlio", "$root/usr/lib/pango/$PANGO/module-files.d/libpango1.0-0.modules"
60     or die "$root/usr/lib/$PANGO/module-files.d/libpango1.0-0.modules: $!";
61     local $/;
62     my $rc = <$fh>;
63     $rc =~ s/^\//$root\//gm; # replace abs paths by relative ones
64    
65     mkdir "$root/pango-modules";
66     open my $fh, ">:perlio", "$root/pango-modules/pango.modules"
67     or die "$root/pango-modules/pango.modules: $!";
68     print $fh $rc;
69    
70     $ENV{PANGO_RC_FILE} = "$root/pango.rc";
71     open my $fh, ">:perlio", $ENV{PANGO_RC_FILE}
72     or die "$ENV{PANGO_RC_FILE}: $!";
73     print $fh "[Pango]\nModuleFiles = $root/pango-modules\n";
74 root 1.60 } else {
75     # OS X
76     $ENV{FC_CONFIG_FILE} = "$root/fonts.conf"; # no effect??!?!
77     $ENV{FC_CONFIG_DIR} = $root; # no effect??!?!
78     $ENV{PANGO_RC_FILE} = "$root/pango.rc";
79     $ENV{DYLD_LIBRARY_PATH} = $root;
80     chdir $root; # for pango modules, maybe other things
81 root 1.69 symlink "libjpeg.62.dylib", "libjpeg.dylib";
82 root 1.1 }
83    
84     unshift @INC, $root;
85     }
86     }
87    
88 root 1.17 # prepend private library directory
89     BEGIN {
90     for (grep !ref, @INC) {
91 root 1.24 my $path = "$_/Deliantra/Client/private";
92 root 1.17 if (-d $path) {
93     unshift @INC, $path;
94     last;
95     }
96     }
97     }
98    
99 root 1.1 # need to do it again because that pile of garbage called PAR nukes it before main
100     unshift @INC, $ENV{PAR_TEMP}
101     if %PAR::LibCache;
102    
103 root 1.4 use EV;
104 root 1.67 BEGIN { *time = \&EV::time }
105    
106 root 1.1 use List::Util qw(max min);
107    
108 root 1.14 use Deliantra;
109     use Deliantra::Protocol::Constants;
110 root 1.1
111 root 1.66 use AnyEvent::Util ();
112 root 1.46 use AnyEvent::DNS;
113 root 1.51 use AnyEvent::Socket ();
114 root 1.46
115 root 1.1 use Compress::LZF;
116 root 1.66 use JSON::XS;
117 root 1.1
118 root 1.44 use DC;
119 root 1.66
120     sub crash($;$) {
121 root 1.67 # nop during compiletime
122 root 1.66 }
123    
124     BEGIN {
125     $SIG{__DIE__} = sub {
126     return if $^S;
127 root 1.68 crash "CRASH/DIE: $_[0]" => 1;
128 root 1.66 DC::fatal Carp::longmess "$_[0]";
129     }
130     }
131    
132 root 1.18 use DC::OpenGL ();
133     use DC::Protocol;
134     use DC::DB;
135     use DC::UI;
136     use DC::UI::Canvas;
137     use DC::UI::Inventory;
138     use DC::UI::SpellList;
139     use DC::UI::Dockable;
140 elmex 1.23 use DC::UI::Dockbar;
141 root 1.18 use DC::UI::ChatView;
142 elmex 1.23 use DC::MessageDistributor;
143 root 1.18 use DC::Pod;
144     use DC::MapWidget;
145     use DC::Macro;
146 root 1.1
147     $SIG{QUIT} = sub { Carp::cluck "QUIT" };
148     $SIG{PIPE} = 'IGNORE';
149    
150 root 1.4 $EV::DIED = sub {
151 root 1.68 crash "CRASH/EV::DIED: $@" => 1;
152 root 1.18 DC::fatal Carp::longmess $@;
153 root 1.1 };
154    
155     my $MAX_FPS = 60;
156    
157     our $META_SERVER = "http://metaserver.schmorp.de/current.json";
158    
159     our $LAST_REFRESH;
160     our $NOW;
161    
162     our $CFG;
163     our $PROFILE; # current profile
164     our $FAST; # fast, low-quality mode, possibly useful for software-rendering
165    
166     our $WANT_REFRESH;
167    
168 root 1.53 our $MODE_SLIDER;
169     our $CAVEAT_LABEL;
170    
171 root 1.1 our @SDL_MODES;
172 root 1.52 our $SDL_REINIT = 1;
173 root 1.1 our $WIDTH;
174     our $HEIGHT;
175     our $FULLSCREEN;
176     our $FONTSIZE;
177    
178     our $FONT_PROP;
179     our $FONT_FIXED;
180    
181 root 1.67 our $CONN;
182    
183 root 1.1 our $MAP;
184     our $MAPMAP;
185     our $MAPWIDGET;
186     our $COMPLETER;
187     our $BUTTONBAR;
188     our $METASERVER;
189     our $LOGIN_BUTTON;
190     our $QUIT_DIALOG;
191     our $HOST_ENTRY;
192     our $FULLSCREEN_ENABLE;
193     our $PICKUP_ENABLE;
194     our $SERVER_INFO;
195    
196     our $SETUP_DIALOG;
197     our $SETUP_NOTEBOOK;
198     our $SETUP_SERVER;
199     our $SETUP_LOGIN;
200     our $SETUP_KEYBOARD;
201    
202     our $PL_NOTEBOOK;
203     our $PL_WINDOW;
204    
205     our $MUSIC_PLAYING_WIDGET;
206     our $LICENSE_WIDGET;
207    
208     our $PICKUP_PAGE;
209     our $INVENTORY_PAGE;
210     our $STATS_PAGE;
211     our $SKILL_PAGE;
212     our $SPELL_PAGE;
213     our $SPELL_LIST;
214    
215     our $HELP_WINDOW;
216     our $MESSAGE_WINDOW;
217 elmex 1.23 our $MESSAGE_DIST;
218 root 1.1 our $FLOORBOX;
219     our $GAUGES;
220     our $STATWIDS;
221    
222     our $SDL_ACTIVE;
223     our %SDL_CB;
224    
225     our $ALT_ENTER_MESSAGE;
226     our $STATUSBOX;
227 root 1.29 our $MODBOX;
228 root 1.1 our $DEBUG_STATUS;
229    
230     our $INV;
231     our $INVR;
232     our $INVR_HB;
233    
234     #############################################################################
235    
236 root 1.67 # write a crash message blockingly to the socket, if possible
237     # this is a bit too complicated for my tastes, but it was easy.
238     *crash = sub($;$) {
239     my ($msg, $backtrace) = @_;
240    
241     return unless $CONN;
242    
243     my $fh = $CONN->{fh}
244     or return;
245    
246     my $buf = delete $CONN->{wbuf};
247    
248     $buf .= pack "n/a*", "exti " . JSON::XS::encode_json [clientlog => undef, substr $msg, 0, 8000];
249    
250     AnyEvent::Util::fh_nonblocking $fh, 0;
251     syswrite $fh, $buf;
252     AnyEvent::Util::fh_nonblocking $fh, 1;
253    
254     $msg =~ s/\s+$//;
255    
256     # backtrace as second step, in case it crashes, too
257 root 1.68 crash Carp::longmess "$msg\nbacktrace, for client version $DC::VERSION, generated"
258 root 1.67 if $backtrace;
259     };
260    
261     #############################################################################
262    
263 root 1.1 sub status {
264 root 1.18 $STATUSBOX->add (DC::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
265 root 1.1 }
266    
267     sub debug {
268     $DEBUG_STATUS->set_text ($_[0]);
269     }
270    
271     sub message {
272 elmex 1.23 $MESSAGE_DIST->message (@_);
273 root 1.1 }
274    
275 root 1.29 sub update_modbox {
276     my $mod = DC::SDL_GetModState;
277    
278     my $markup;
279    
280     $markup .= $mod & DC::KMOD_CTRL
281     ? ($MAPWIDGET->{ctrl} ? "[REPEAT]" : "[<span foreground='#888'>REPEAT</span>]")
282     : "[<span foreground='#888'> once </span>]";
283    
284     $markup .= $mod & DC::KMOD_SHIFT
285     ? ($MAPWIDGET->{shft} ? "[FIRE]" : "[<span foreground='#888'>FIRE</span>]")
286     : "[<span foreground='#888'>move</span>]";
287    
288     $markup .= $mod & (DC::KMOD_ALT | DC::KMOD_META)
289     ? "[ALT]"
290     : "[<span foreground='#888'>alt</span>]";
291    
292     $markup .= $mod & DC::KMOD_NUM
293     ? "[NUM]"
294     : "[<span foreground='#888'>num</span>]";
295    
296 root 1.30 # <tt> around next statement works around some bug that keeps the
297     # "font =>" from being used on windows
298     $MODBOX->set_markup ("<tt>$markup</tt>");
299 root 1.29 }
300    
301 root 1.1 #############################################################################
302     #TODO: maybe move into own audio module...
303    
304     our $SDL_MIXER;
305    
306     our $MUSIC_DEFAULT = "in_a_heartbeat.ogg";
307     our $MUSIC_WANT; # arryref of ambient music we want to play
308     our @MUSIC_HAVE; # ambient music we have on disk
309     our $MUSIC_START;
310     our @MUSIC_JINGLE; # which jingles to play next
311     our $MUSIC_PLAYING_DATA;
312     our $MUSIC_PLAYING_META;
313     our $MUSIC_PLAYER;
314     our $MUSIC_RESUME = 30; # resume music when played less than these many seconds before
315    
316     our %AUDIO_CHUNK; # audio "files"
317     our %AUDIO_PLAY; # which audio faces should be played
318    
319     sub audio_channel_finished {
320     my ($channel) = @_;
321    
322     # warn "channel $channel finished\n";#d#
323     }
324    
325     sub audio_sound_push($) {
326     my ($face) = @_;
327    
328     $CFG->{effects_enable}
329     or return;
330    
331     $AUDIO_PLAY{$face}
332     or return;
333    
334     if (my $chunk = $AUDIO_CHUNK{$face}) {
335 root 1.4 for (grep $_->[0] >= EV::now, @{(delete $AUDIO_PLAY{$face}) || []}) {
336 root 1.1 my (undef, $dx, $dy, $vol) = @$_;
337    
338 root 1.18 my $channel = DC::Channel::find;
339 root 1.1 $channel->volume ($vol * $CFG->{effects_volume} * 128 / 255);
340     $channel->set_position_r ($dx, $dy, 20);
341     $chunk->play ($channel);
342     }
343     } else {
344     # sound_meta not set means data is in flight either way
345     my $meta = $CONN->{face}[$face]
346     or return;
347    
348     $meta->{data}
349     or return;
350    
351     # if its a jingle, play it as ambient music
352     if ($meta->{data}{jingle}) {
353     if (delete $AUDIO_PLAY{$face}) { # take the jingle out of the sound queue
354     push @MUSIC_JINGLE, $meta; # push it oto the music/jingle queue
355     &audio_music_push ($face);
356     }
357     } else {
358     # fetch from database
359 root 1.18 DC::DB::get res_data => $meta->{name}, sub {
360     my $rwops = new DC::RW $_[0];
361     my $chunk = new DC::MixChunk $rwops
362     or Carp::confess "sound face " . (JSON::XS::encode_json $meta) . " unloadable: " . DC::Mix_GetError;
363 root 1.1 $chunk->volume (($meta->{data}{volume} || 1) * 128);
364     $AUDIO_CHUNK{$face} = $chunk;
365    
366     audio_sound_push ($face);
367     };
368     }
369     }
370     }
371    
372     sub audio_sound_play {
373     my ($face, $dx, $dy, $vol) = @_;
374    
375     $SDL_MIXER
376     or return;
377     $CFG->{effects_enable}
378     or return;
379    
380     my $queue = $AUDIO_PLAY{$face} ||= [];
381 root 1.4 push @$queue, [EV::now + 0.6, $dx, $dy, $vol]; # do not play sound for outdated events
382 root 1.1 audio_sound_push $face
383     unless @$queue > 1;
384     }
385    
386     sub audio_music_set_meta {
387     my ($meta) = @_;
388    
389     $MUSIC_PLAYING_META = $meta;
390     $MUSIC_PLAYING_WIDGET->set_markup (
391 root 1.18 "<b>Name</b>: " . (DC::asxml $meta->{data}{name}) . "\n"
392     . "<b>Author</b>: " . (DC::asxml $meta->{data}{author}) . "\n"
393     . "<b>Source</b>: " . (DC::asxml $meta->{data}{source}) . "\n"
394     . "<b>License</b>: " . (DC::asxml $meta->{data}{license})
395 root 1.1 );
396     }
397    
398     sub audio_music_update_volume {
399     return unless $MUSIC_PLAYING_META;
400     my $volume = $MUSIC_PLAYING_META->{data}{volume} || 1;
401     my $base = $MUSIC_PLAYING_META->{data}{jingle} ? 1 : $CFG->{bgm_volume};
402 root 1.18 DC::MixMusic::volume $base * $volume * 128;
403 root 1.1 }
404    
405     sub audio_music_start {
406     my $meta = $MUSIC_PLAYING_META;
407    
408 root 1.18 DC::DB::get res_data => $meta->{name}, sub {
409 root 1.1 return unless $SDL_MIXER;
410    
411     # music might have changed...
412     $meta eq $MUSIC_PLAYING_META
413     or return &audio_music_start ();
414    
415     audio_music_update_volume;
416    
417     $MUSIC_PLAYING_DATA = \$_[0];
418    
419     my $rwops = $meta->{path}
420 root 1.18 ? new_from_file DC::RW $meta->{path}
421     : new DC::RW $$MUSIC_PLAYING_DATA;
422 root 1.1
423 root 1.18 $MUSIC_PLAYER = new DC::MixMusic $rwops
424     or Carp::confess "music face $meta->{face} unloadable: " . DC::Mix_GetError;
425 root 1.1
426     my $NOW = time;
427    
428     if ($MUSIC_PLAYING_META->{stop_time} > $NOW - $MUSIC_RESUME) {
429     my $pos = $MUSIC_PLAYING_META->{stop_pos};
430     $MUSIC_PLAYER->fade_in_pos (0, 700, $pos);
431     $MUSIC_START = time - $pos;
432     } else {
433     $MUSIC_PLAYER->play (0);
434     $MUSIC_START = time;
435     }
436    
437     delete $meta->{stop_time};
438     delete $meta->{stop_pos};
439     }
440     }
441    
442     sub audio_music_push {
443     return unless $SDL_MIXER;
444    
445     my $fade_out;
446    
447     if (@MUSIC_JINGLE) {
448 root 1.9 $fade_out = 333;
449 root 1.1 @MUSIC_HAVE = $MUSIC_JINGLE[0];
450 root 1.9
451 root 1.1 } else {
452     return unless $CFG->{bgm_enable};
453    
454 root 1.9 $fade_out = 700;
455    
456     @MUSIC_HAVE =
457 root 1.1 grep $_ && $_->{data},
458     map $CONN->{face}[$_],
459     @$MUSIC_WANT;
460    
461     # randomize music a bit so that the order is not always the same
462 root 1.9 $_->{stop_time} ||= rand for @MUSIC_HAVE;
463 root 1.1
464     # default MUSIC_HAVE == MUSIC_DEFAULT
465 root 1.18 @MUSIC_HAVE = { path => DC::find_rcfile "music/$MUSIC_DEFAULT" }
466 root 1.9 unless @MUSIC_HAVE;
467 root 1.1 }
468    
469     # if the currently playing song is acceptable, let it continue
470     return if grep $MUSIC_PLAYING_META == $_, @MUSIC_HAVE;
471    
472     my $NOW = time;
473    
474     if ($MUSIC_PLAYING_META) {
475     $MUSIC_PLAYING_META->{stop_time} = $NOW;
476     $MUSIC_PLAYING_META->{stop_pos} = $NOW - $MUSIC_START;
477 root 1.18 DC::MixMusic::fade_out $fade_out;
478 root 1.1 } else {
479     # sort by stop time, oldest first
480     @MUSIC_HAVE = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_HAVE;
481    
482     # if the most recently-played piece played very recently,
483     # resume it, else choose the oldest piece for rotation.
484     audio_music_set_meta
485     $MUSIC_HAVE[-1]{stop_pos} && $MUSIC_HAVE[-1]{stop_time} > $NOW - $MUSIC_RESUME
486     ? $MUSIC_HAVE[-1]
487     : $MUSIC_HAVE[0];
488    
489     audio_music_start;
490     }
491     }
492    
493     sub audio_music_set_ambient {
494     my ($songs) = @_;
495    
496     $MUSIC_WANT = $songs;
497     audio_music_push;
498     }
499    
500     sub audio_music_finished {
501     if ($MUSIC_PLAYING_META) {
502     $MUSIC_PLAYING_META->{stop_time} = time;
503     }
504    
505     # we compress multiple jingles of the same type
506     shift @MUSIC_JINGLE
507     while @MUSIC_JINGLE && $MUSIC_PLAYING_META == $MUSIC_JINGLE[0];
508    
509     $MUSIC_PLAYING_WIDGET->clear;
510    
511     undef $MUSIC_PLAYER;
512     undef $MUSIC_PLAYING_META;
513     undef $MUSIC_PLAYING_DATA;
514    
515     audio_music_push;
516     }
517    
518     sub audio_init {
519     if ($CFG->{audio_enable}) {
520     $ENV{MIX_EFFECTSMAXSPEED} = 1;
521 root 1.18 $SDL_MIXER = !DC::Mix_OpenAudio
522 root 1.1 $CFG->{audio_hw_frequency},
523 root 1.18 DC::MIX_DEFAULT_FORMAT,
524 root 1.1 $CFG->{audio_hw_channels},
525     $CFG->{audio_hw_chunksize};
526    
527     if ($SDL_MIXER) {
528 root 1.18 DC::Mix_AllocateChannels $CFG->{audio_mix_channels};
529 root 1.1
530     audio_music_finished;
531     } else {
532     status "Unable to open sound device: there will be no sound";
533     }
534     } else {
535     undef $SDL_MIXER;
536     }
537    
538     sub audio_tab_update;
539     audio_tab_update;
540     }
541    
542     sub audio_shutdown {
543     undef $MUSIC_PLAYER;
544     undef $MUSIC_PLAYING_META;
545     undef $MUSIC_PLAYING_DATA;
546    
547     $MUSIC_WANT = [];
548     @MUSIC_JINGLE = ();
549     %AUDIO_PLAY = ();
550     %AUDIO_CHUNK = ();
551    
552 root 1.18 DC::Mix_CloseAudio if $SDL_MIXER;
553 root 1.1 undef $SDL_MIXER;
554     }
555    
556     #############################################################################
557    
558     sub destroy_query_dialog {
559     (delete $_[0]{query_dialog})->destroy
560     if $_[0]{query_dialog};
561     }
562    
563     # FIXME: a very ugly hack to wait for stat update look below! #d#
564     our $QUERY_TIMER; #d#
565    
566     # server query dialog
567     sub server_query {
568     my ($conn, $flags, $prompt) = @_;
569    
570     # FIXME: a very ugly hack to wait for stat update #d#
571     if ($prompt =~ /roll new stats/ and not $conn->{stat_change_with}) {
572     unless ($QUERY_TIMER) {
573 root 1.10 $QUERY_TIMER = EV::timer 1, 0, sub {
574     server_query ($conn, $flags, $prompt, 1);
575     $QUERY_TIMER = undef
576     };
577    
578 root 1.1 return;
579     }
580     }
581    
582 root 1.18 $conn->{query_dialog} = my $dialog = new DC::UI::Toplevel
583 root 1.1 x => "center",
584     y => "center",
585     title => "Server Query",
586 root 1.18 child => my $vbox = new DC::UI::VBox,
587 root 1.1 ;
588    
589 root 1.18 my @dialog = my $label = new DC::UI::Label
590 root 1.1 max_w => $::WIDTH * 0.8,
591     ellipsise => 0,
592     text => $prompt;
593    
594     if ($flags & CS_QUERY_YESNO) {
595 root 1.18 push @dialog, my $hbox = new DC::UI::HBox;
596 root 1.1
597 root 1.18 $hbox->add (new DC::UI::Button
598 root 1.1 text => "No",
599     on_activate => sub {
600     $conn->send ("reply n");
601     $dialog->destroy;
602     0
603     }
604     );
605 root 1.18 $hbox->add (new DC::UI::Button
606 root 1.1 text => "Yes",
607     on_activate => sub {
608     $conn->send ("reply y");
609     destroy_query_dialog $conn;
610     0
611     },
612     );
613    
614     $dialog->grab_focus;
615    
616     } elsif ($flags & CS_QUERY_SINGLECHAR) {
617     if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
618     $dialog->{tooltip} = "#charcreation_focus";
619    
620 root 1.18 unshift @dialog, new DC::UI::Label
621 root 1.1 max_w => $::WIDTH * 0.8,
622     ellipsise => 0,
623     markup => "\nOr use your keyboard and the text entry below:\n";
624    
625 root 1.18 unshift @dialog, my $table = new DC::UI::Table;
626 root 1.1
627 root 1.18 $table->add_at (0, 0, new DC::UI::Button
628 root 1.1 text => "Next Race",
629     on_activate => sub {
630     $conn->send ("reply n");
631     destroy_query_dialog $conn;
632     0
633     },
634     );
635 root 1.18 $table->add_at (2, 0, new DC::UI::Button
636 root 1.1 text => "Accept",
637     on_activate => sub {
638     $conn->send ("reply d");
639     destroy_query_dialog $conn;
640     0
641     },
642     );
643    
644     if ($conn->{chargen_race_description}) {
645 root 1.18 unshift @dialog, new DC::UI::Label
646 root 1.1 max_w => $::WIDTH * 0.8,
647     ellipsise => 0,
648     markup => "<span foreground='#ccccff'>$conn->{chargen_race_description}</span>",
649     ;
650     }
651    
652 root 1.18 unshift @dialog, new DC::UI::Face
653 root 1.1 face => $conn->{player}{face},
654     bg => [.2, .2, .2, 1],
655     min_w => 64,
656     min_h => 64,
657     ;
658    
659     if ($conn->{chargen_race_title}) {
660 root 1.18 unshift @dialog, new DC::UI::Label
661 root 1.1 allign => 1,
662     ellipsise => 0,
663     markup => "<span foreground='#ccccff' size='large'>Race: $conn->{chargen_race_title}</span>",
664     ;
665     }
666    
667 root 1.18 unshift @dialog, new DC::UI::Label
668 root 1.1 max_w => $::WIDTH * 0.4,
669     ellipsise => 0,
670 root 1.18 markup => (DC::Pod::section_label ui => "chargen_race"),
671 root 1.1 ;
672    
673     } elsif ($prompt =~ /roll new stats/) {
674     if (my $stat = delete $conn->{stat_change_with}) {
675     $conn->send ("reply $stat");
676     destroy_query_dialog $conn;
677     return;
678     }
679    
680 root 1.18 unshift @dialog, new DC::UI::Label
681 root 1.1 max_w => $::WIDTH * 0.4,
682     ellipsise => 0,
683     markup => "\nOr use your keyboard and the text entry below:\n";
684    
685 root 1.18 unshift @dialog, my $table = new DC::UI::Table;
686 root 1.1
687     # left: re-roll
688 root 1.18 $table->add_at (0, 0, new DC::UI::Button
689 root 1.1 text => "Roll Again",
690     on_activate => sub {
691     $conn->send ("reply y");
692     destroy_query_dialog $conn;
693     0
694     },
695     );
696    
697     # center: swap stats
698 root 1.18 my ($sw1, $sw2) = map +(new DC::UI::Selector
699 root 1.1 expand => 1,
700     value => $_,
701     options => [
702     [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
703     [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
704     [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
705     [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
706     [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
707     [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
708     [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
709     ],
710     ), 1 .. 2;
711    
712 root 1.18 $table->add_at (2, 0, new DC::UI::Button
713 root 1.1 text => "Swap Stats",
714     on_activate => sub {
715     $conn->{stat_change_with} = $sw2->{value};
716     $conn->send ("reply $sw1->{value}");
717     destroy_query_dialog $conn;
718     0
719     },
720     );
721 root 1.18 $table->add_at (2, 1, new DC::UI::HBox children => [$sw1, $sw2]);
722 root 1.1
723     # right: accept
724 root 1.18 $table->add_at (4, 0, new DC::UI::Button
725 root 1.1 text => "Accept",
726     on_activate => sub {
727     $conn->send ("reply n");
728     destroy_query_dialog $conn;
729     0
730     },
731     );
732    
733 root 1.18 unshift @dialog, my $hbox = new DC::UI::HBox;
734 root 1.1 for (
735     [Str => CS_STAT_STR],
736     [Dex => CS_STAT_DEX],
737     [Con => CS_STAT_CON],
738     [Int => CS_STAT_INT],
739     [Wis => CS_STAT_WIS],
740     [Pow => CS_STAT_POW],
741     [Cha => CS_STAT_CHA],
742     ) {
743     my ($name, $id) = @$_;
744 root 1.18 $hbox->add (new DC::UI::Label
745 root 1.1 markup => "$conn->{stat}{$id} <span foreground='yellow'>$name</span>",
746     expand => 1,
747     can_events => 1,
748     can_hover => 1,
749     tooltip => "#stat_$name",
750     );
751     }
752    
753 root 1.18 unshift @dialog, new DC::UI::Label
754 root 1.1 max_w => $::WIDTH * 0.4,
755     ellipsise => 0,
756 root 1.18 markup => (DC::Pod::section_label ui => "chargen_stats"),
757 root 1.1 ;
758     }
759    
760 root 1.18 push @dialog, my $entry = new DC::UI::Entry
761 root 1.1 on_changed => sub {
762     $conn->send ("reply $_[1]");
763     destroy_query_dialog $conn;
764     0
765     },
766     ;
767    
768     $entry->grab_focus;
769    
770     } else {
771     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
772    
773 root 1.18 push @dialog, my $entry = new DC::UI::Entry
774 root 1.1 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
775     on_activate => sub {
776     $conn->send ("reply $_[1]");
777     destroy_query_dialog $conn;
778     0
779     },
780     ;
781    
782     $entry->grab_focus;
783     }
784    
785     $vbox->add (@dialog);
786     $dialog->show;
787     }
788    
789 root 1.46 sub dc_connect {
790     my ($host, $port) = @_;
791 root 1.1
792     my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
793    
794 root 1.44 $CONN =
795 root 1.18 new DC::Protocol
796 root 1.1 host => $host,
797 root 1.51 port => $port,
798 root 1.1 user => $PROFILE->{user},
799     pass => $PROFILE->{password},
800     mapw => $mapsize,
801     maph => $mapsize,
802    
803 root 1.26 client => "$DC::VERSION $] $^O",
804 root 1.1
805     map_widget => $MAPWIDGET,
806     statusbox => $STATUSBOX,
807     map => $MAP,
808     mapmap => $MAPMAP,
809     query => \&server_query,
810    
811     setup_req => {
812     smoothing => $CFG->{map_smoothing}*1,
813     },
814    
815 root 1.44 on_connect => sub {
816     if ($_[0]) {
817     DC::lowdelay fileno $CONN->{fh};
818 root 1.1
819 root 1.44 status "login successful";
820     } else {
821     undef $CONN;
822     status "unable to connect: $!";
823     stop_game();
824     }
825     },
826     ;
827 root 1.1 }
828    
829 root 1.46 sub start_game {
830     status "logging in...";
831    
832     $LOGIN_BUTTON->set_text ("Logout");
833     $SETUP_DIALOG->hide;
834    
835 root 1.51 my ($host, $port) = AnyEvent::Socket::parse_hostport $PROFILE->{host}, "deliantra=13327";
836 root 1.46
837     $MAP = new DC::Map;
838    
839     # hack to make SURE we find the IP address all right
840     # can be removed once AnyEvent::DNS is proven stable.
841     if ($host eq "gameserver.deliantra.net") {
842     AnyEvent::DNS::a "dnstest.deliantra.net", sub {
843     if ($_[0] ne "80.101.114.108") { # Perl
844     status "dns failure, using hardcoded address";
845     $host = "129.13.162.95";
846     }
847    
848     dc_connect $host, $port;
849     };
850     } else {
851     dc_connect $host, $port;
852     }
853     }
854    
855 root 1.1 sub stop_game {
856 root 1.66 crash "stop_game";
857    
858 root 1.1 $LOGIN_BUTTON->set_text ("Login / Register");
859     $SETUP_NOTEBOOK->set_current_page ($SETUP_LOGIN);
860     $SETUP_DIALOG->show;
861     $PL_WINDOW->hide;
862     $SPELL_LIST->clear_spells;
863 root 1.18 $DC::UI::ROOT->emit (stop_game => ! ! $CONN);
864 root 1.1
865     &audio_music_set_ambient ([]);
866    
867     return unless $CONN;
868    
869     status "connection closed";
870    
871     destroy_query_dialog $CONN;
872     $CONN->destroy;
873     $CONN = 0; # false, does not autovivify
874    
875     undef $MAP;
876     }
877    
878     sub graphics_setup {
879 root 1.18 my $vbox = new DC::UI::VBox;
880 root 1.1
881 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]);
882 root 1.1
883     my $row = 0;
884    
885 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "OpenGL Info");
886     $table->add_at (1, $row++, new DC::UI::Label fontsize => 0.8, text => DC::OpenGL::gl_vendor . ", " . DC::OpenGL::gl_version,
887 root 1.64 can_events => 1,
888     tooltip => "<tt><span size='8192'>" . (DC::OpenGL::gl_extensions) . "</span></tt>");
889 root 1.1
890 root 1.53 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Caveats");
891     $table->add_at (1, $row++, $CAVEAT_LABEL = new DC::UI::Label fontsize => 0.8,
892     can_events => 1,
893     tooltip => "This field shows any known issues with your config or driver, such as "
894     . "a non-accelerated display format. You can try to work around these issues "
895     . "by selecting a different video mode, changing the settings below or "
896     . "by installing the right driver for your graphics card.");
897    
898 root 1.1 my $vidmode_tooltip =
899     "<b>Video Mode.</b> The video mode to use for fullscreen (and the window size for windowed operation). "
900     . "The format is <i>width</i> x <i>height</i> \@ <i>depth-per-channel</i> + <i>alpha-channel</i>.";
901    
902 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Video Mode");
903 root 1.18 $table->add_at (1, $row++, my $hbox = new DC::UI::HBox);
904 root 1.1
905 root 1.53 $hbox->add ($MODE_SLIDER = new DC::UI::Slider
906     force_w => $WIDTH * 0.1, expand => 1,
907 root 1.56 range => [ ($CFG->{sdl_mode}) x 3 ],
908 root 1.1 tooltip => $vidmode_tooltip);
909 root 1.18 $hbox->add (my $mode_label = new DC::UI::Label
910 root 1.22 height => 0.8, template => "9999x9999@9+9",
911 root 1.1 can_events => 1, tooltip => $vidmode_tooltip);
912    
913 root 1.53 $MODE_SLIDER->connect (changed => sub {
914 root 1.1 my ($self, $value) = @_;
915    
916     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
917     $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
918     });
919 root 1.53 $MODE_SLIDER->emit (changed => $MODE_SLIDER->{range}[0]);
920 root 1.1
921 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fullscreen");
922 root 1.18 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new DC::UI::CheckBox
923 root 1.1 state => $CFG->{fullscreen},
924     tooltip => "Bring the client into fullscreen mode.",
925     on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
926     );
927    
928 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Force OpenGL 1.1");
929 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
930 root 1.1 state => $CFG->{force_opengl11},
931 elmex 1.23 tooltip => "Limit Deliantra to use OpenGL 1.1 features only. This will normally result in "
932 root 1.1 . "higher memory usage and slower performance. It will, however, help tremendously on "
933     . "cards that claim to support a feature but fall back to software rendering. "
934     . "Nvidia Geforce FX cards are known to claim features the hardware doesn't support, "
935 root 1.52 . "but cards and drivers from other vendors (ATI) are often just as bad. "
936     . "<b>If you experience extremely low framerates and your card should do better, try this option.</b>",
937 root 1.1 on_changed => sub { my ($self, $value) = @_; $CFG->{force_opengl11} = $value; 0 }
938     );
939    
940 root 1.52 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Forbid Alpha");
941     $table->add_at (1, $row++, new DC::UI::CheckBox
942     state => $CFG->{disable_alpha},
943     tooltip => "Forbid off the use of the alpha channel. This makes Deliantra look a lot worse "
944     . "by disabling a number of textures and transparency effects. Normally, these "
945     . "effects do not cost a lot of resources, but some graphics cards might fall "
946     . "back to etxremely slow rendering if this is enabled. If disabling this option "
947     . "noticably improves the framerate of the client please report this! "
948     . "<b>If you experience extremely low framerates and your card should do better, try this option.</b>",
949     on_changed => sub {
950     my ($self, $value) = @_;
951     $CFG->{disable_alpha} = $value;
952     $SDL_REINIT = 1; # SDL_SetVideoMode ignores GL attr changes
953     0
954     }
955     );
956    
957 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Compress Textures");
958 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
959 root 1.1 state => $CFG->{texture_compression},
960     tooltip => "Use texture compression. Normally this will not reduce visual quality noticable but "
961 root 1.64 . "will save a lot of memory and increase performance (and also fall prey to the ever-buggy Mac OS X software renderer). "
962     . "The compression algorithm can differ form card to card, so your mileage may vary. This setting is ignored in "
963     . "forced OpenGL 1.1 mode and when using the Apple renderer.",
964 root 1.1 on_changed => sub { my ($self, $value) = @_; $CFG->{texture_compression} = $value; 0 }
965     );
966    
967 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fast & Ugly");
968 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
969 root 1.1 state => $CFG->{fast},
970     tooltip => "Lower the visual quality considerably to speed up rendering.",
971     on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
972     );
973    
974 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "GUI Fontsize");
975 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
976 root 1.1 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
977     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
978     on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
979     );
980    
981 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
982 root 1.22 expand => 1, text => "Apply",
983 root 1.1 tooltip => "Apply the video settings above.",
984     on_activate => sub {
985     video_shutdown ();
986     video_init ();
987     0
988     }
989     );
990    
991 root 1.56 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Smooth Movement");
992     $table->add_at (1, $row++, new DC::UI::CheckBox
993     state => $CFG->{smooth_movement},
994     tooltip => "<b>Smooth Movement</b> tries to make movement, well, smoother, but also increases the framerate. "
995     . "If you have a very slow system, non-accelerated drivers or plain dislike smooth scrolling, "
996     . "then disable this option. Changes take effect immdiately.",
997     on_changed => sub { my ($self, $value) = @_; $CFG->{smooth_movement} = $value; 0 }
998     );
999    
1000 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Scale");
1001 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1002 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
1003     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
1004     on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
1005     );
1006    
1007 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Smoothing");
1008 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1009 root 1.1 state => $CFG->{map_smoothing},
1010     tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
1011     . "This increases load on the graphics subsystem and works only with TRT servers. "
1012     . "Changes take effect at next login only.",
1013     on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
1014     );
1015    
1016 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fog of War");
1017 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1018 root 1.1 state => $CFG->{fow_enable},
1019     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
1020     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
1021     );
1022    
1023 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "FoW Intensity");
1024 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1025 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
1026     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
1027     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
1028     );
1029    
1030 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Fontsize");
1031 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1032 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
1033     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant, "
1034     . "but you still need to press apply to correctly re-layout the widget.",
1035 elmex 1.23 on_changed => sub { $MESSAGE_DIST->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
1036 root 1.1 );
1037    
1038 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge fontsize");
1039 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1040 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
1041     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
1042     on_changed => sub {
1043     $CFG->{gauge_fontsize} = $_[1];
1044     &set_gauge_window_fontsize;
1045     0
1046     }
1047     );
1048    
1049 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge size");
1050 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1051 root 1.1 range => [$CFG->{gauge_size}, 0.2, 0.8],
1052     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
1053     on_changed => sub {
1054     $CFG->{gauge_size} = $_[1];
1055     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
1056     0
1057     }
1058     );
1059    
1060     $vbox
1061     }
1062    
1063     our $AUDIO_HW_CHUNKSIZE;
1064     our $AUDIO_INFO;
1065    
1066     sub audio_tab_update {
1067 root 1.18 my ($freq, $format, $chans) = DC::Mix_QuerySpec;
1068 root 1.1
1069     $AUDIO_HW_CHUNKSIZE->set_options ([
1070     [0, "default", "Use System Default"],
1071     map {
1072     my $ms = sprintf "%dms", 1000 * $_ / ($CFG->{audio_hw_frequency} || 22050);
1073     [$_, $ms, "$ms ($_ samples)"],
1074     } 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
1075     ]);
1076    
1077     my $text = !$freq
1078     ? "audio is off"
1079     : "audio is enabled\n"
1080     . "frequency (Hz): $freq\n"
1081     . "channels: $chans";
1082    
1083     $AUDIO_INFO->set_text ($text);
1084     }
1085    
1086     sub audio_setup {
1087 root 1.18 my $vbox = new DC::UI::VBox;
1088 root 1.1
1089 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 0, 1]);
1090 root 1.1
1091     my $row = 0;
1092    
1093 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Audio Enable");
1094 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1095 root 1.1 state => $CFG->{audio_enable},
1096     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.",
1097     on_changed => sub { $CFG->{audio_enable} = $_[1]; 1 }
1098     );
1099    
1100 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Sound Effects");
1101 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
1102 root 1.1 expand => 1, state => $CFG->{effects_enable},
1103     tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
1104     on_changed => sub {
1105     $CFG->{effects_enable} = $_[1];
1106     $CONN->update_fx_want if $CONN;
1107     1
1108     }
1109     );
1110 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
1111 root 1.1 expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
1112     tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
1113     . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
1114     on_changed => sub { $CFG->{effects_volume} = $_[1]; 1 }
1115     );
1116    
1117 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Background Music");
1118 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
1119 root 1.1 expand => 1, state => $CFG->{bgm_enable},
1120     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
1121     on_changed => sub {
1122     $CFG->{bgm_enable} = $_[1];
1123     $CONN->update_fx_want if $CONN;
1124     audio_music_push;
1125     1
1126     }
1127     );
1128 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
1129 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
1130     tooltip => "The volume of the background music. Changes are instant.",
1131     on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
1132     );
1133    
1134 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Frequency");
1135 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1136 root 1.1 c_colspan => 2, expand => 1,
1137     value => $CFG->{audio_hw_frequency},
1138     options => [
1139     [ 0, "default" , "Use System Default"],
1140     [11025, "11 kHz" , "11kHz (low quality)"],
1141     [22050, "22 kHz" , "22kHz (reduced quality)"],
1142     [44100, "44.1 kHz", "44.1kHz (cd quality)"],
1143     [48000, "48 kHz" , "48kHz (studio quality)"],
1144     ],
1145     tooltip => "The sampling frequency to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1146     on_changed => sub {
1147     $CFG->{audio_hw_frequency} = $_[1];
1148     audio_tab_update;
1149     1
1150     }
1151     );
1152    
1153 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Channels");
1154 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1155 root 1.1 c_colspan => 2, expand => 1,
1156     value => $CFG->{audio_hw_channels},
1157     options => [
1158     [0, "default" , "Use System Default"],
1159     [1, "Mono" , "Mono (single channel, low quality)"],
1160 root 1.6 [2, "Stereo" , "Stereo (dual channel, standard quality)"],
1161 root 1.1 [4, "4 Ch Surround", "4 Channel Surround Sound (3d sound, high quality)"],
1162     [6, "6 Ch Surround", "6 Channel Surround Sound (3d sound + center + lfe)"],
1163     ],
1164     tooltip => "The number of independent sound channels to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1165     on_changed => sub {
1166     $CFG->{audio_hw_channels} = $_[1];
1167     audio_tab_update;
1168     1
1169     }
1170     );
1171    
1172 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Latency");
1173 root 1.18 $table->add_at (1, $row++, $AUDIO_HW_CHUNKSIZE = new DC::UI::Selector
1174 root 1.1 c_colspan => 2, expand => 1,
1175     value => $CFG->{audio_hw_chunksize},
1176     tooltip => "The guarenteed latency. Lower is better, but also more cpu-intensive and might cause stuttering. If music playback "
1177     . "is stuttering, increase this value. Values of 50-100ms are optimal.",
1178     on_changed => sub {
1179     $CFG->{audio_hw_chunksize} = $_[1];
1180     audio_tab_update;
1181     1
1182     }
1183     );
1184    
1185     # should really be a slider
1186 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Mixer Voices");
1187 root 1.18 $table->add_at (1, $row++, new DC::UI::ValSlider
1188 root 1.1 c_colspan => 2, expand => 1,
1189     tooltip => "The number of simultaneous sound effects possible. Higher is better, but also more cpu-intensive and might cause stuttering.",
1190     range => [$::CFG->{audio_mix_channels}, 4, 32, 0, 1],
1191     template => ">= 99",
1192     on_changed => sub {
1193     my ($slider, $value) = @_;
1194    
1195     $CFG->{audio_mix_channels} = $value
1196     if $value;
1197     1;
1198     }
1199     );
1200    
1201 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
1202 root 1.22 c_colspan => 2, expand => 1, text => "Apply",
1203 root 1.1 tooltip => "Apply the audio settings",
1204     on_activate => sub {
1205     audio_shutdown ();
1206     audio_init ();
1207     0
1208     }
1209     );
1210    
1211 root 1.18 $vbox->add (new DC::UI::FancyFrame
1212 root 1.1 expand => 1,
1213     label => "Audio Info",
1214 root 1.18 child => ($AUDIO_INFO = new DC::UI::Label ellipsise => 0),
1215 root 1.1 );
1216    
1217     audio_tab_update;
1218    
1219     $vbox
1220     }
1221    
1222     sub set_gauge_window_fontsize {
1223     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1224     $_->set_fontsize ($::CFG->{gauge_fontsize});
1225     }
1226     }
1227    
1228     sub make_gauge_window {
1229     my $gh = int $HEIGHT * $CFG->{gauge_size};
1230    
1231 root 1.18 my $win = new DC::UI::Frame (
1232 root 1.1 force_x => 0,
1233     force_y => "max",
1234     force_w => $WIDTH,
1235     force_h => $gh,
1236     );
1237    
1238 root 1.18 $win->add (my $hbox = new DC::UI::HBox
1239 root 1.1 children => [
1240 root 1.18 (new DC::UI::HBox expand => 1),
1241     (new DC::UI::VBox children => [
1242     (new DC::UI::Empty expand => 1),
1243     (new DC::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new DC::UI::Table)),
1244 root 1.1 ]),
1245 root 1.18 (my $vbox = new DC::UI::VBox),
1246 root 1.1 ],
1247     );
1248    
1249 root 1.18 $vbox->add (new DC::UI::HBox
1250 root 1.1 expand => 1,
1251     children => [
1252 root 1.18 (new DC::UI::Empty expand => 1),
1253     (my $hb = new DC::UI::HBox),
1254 root 1.1 ],
1255     );
1256    
1257 root 1.18 $hb->add (my $hg = new DC::UI::Gauge type => 'hp', tooltip => "#stat_health");
1258     $hb->add (my $mg = new DC::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1259     $hb->add (my $gg = new DC::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1260     $hb->add (my $fg = new DC::UI::Gauge type => 'food', tooltip => "#stat_food");
1261    
1262 root 1.22 $vbox->add (my $exp = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1263 root 1.18 $vbox->add (my $prg = new DC::UI::ExperienceProgress);
1264     $vbox->add (my $sklprg = new DC::UI::ExperienceProgress);
1265 root 1.22 $vbox->add (my $rng = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1266 root 1.1
1267     $GAUGES = {
1268     exp => $exp, prg => $prg, sklprg => $sklprg,
1269     win => $win, range => $rng,
1270     hp => $hg, mana => $mg, grace => $gg, food => $fg,
1271     };
1272    
1273     &set_gauge_window_fontsize;
1274    
1275     $win
1276     }
1277    
1278     sub debug_setup {
1279 root 1.18 my $table = new DC::UI::Table;
1280 root 1.1
1281 root 1.18 $table->add_at (0, 0, new DC::UI::Label text => "Widget Borders");
1282     $table->add_at (1, 0, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1283     $table->add_at (0, 1, new DC::UI::Label text => "Tooltip Widget Info");
1284     $table->add_at (1, 1, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1285     $table->add_at (0, 2, new DC::UI::Label text => "Show FPS");
1286     $table->add_at (1, 2, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1287     $table->add_at (0, 3, new DC::UI::Label text => "Suppress Tooltips");
1288     $table->add_at (1, 3, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1289     $table->add_at (0, 4, new DC::UI::Button text => "die on click(tm)", on_activate => sub { &DC::debug() } );
1290    
1291 root 1.21 $table->add_at (0, 5, new DC::UI::TextEdit text => "line1\0152\0153\nµikachu\nづx゙つ゛");#d#
1292 root 1.18
1293     $table->add_at (7,7, my $t = new DC::UI::Table expand => 0);
1294 root 1.20 $t->add_at (0,0, new DC::UI::Label text => "a a", c_rowspan => 1, c_colspan => 2);
1295     $t->add_at (2,0, new DC::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1296     $t->add_at (1,2, new DC::UI::Label text => "c c", c_rowspan => 1, c_colspan => 2);
1297     $t->add_at (0,1, new DC::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1298 root 1.18 $t->add_at (1,1, new DC::UI::Label text => "e");
1299 root 1.1
1300 root 1.18 $table->add_at (7, 6, my $c = new DC::UI::Canvas);
1301 root 1.1
1302     $c->add_items ({
1303     type => "line_loop",
1304     color => [0, 1, 0],
1305     width => 9,
1306     coord_mode => "abs",
1307     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1308     });
1309    
1310     $c->add_items ({
1311     type => "lines",
1312     color => [1, 1, 0],
1313     width => 2,
1314     coord_mode => "rel",
1315     coord => [[0,0], [1,1], [1,0], [0,1]],
1316     });
1317    
1318     $c->add_items ({
1319     type => "polygon",
1320     color => [0, 0.43, 0],
1321     width => 2,
1322     coord_mode => "rel",
1323     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1324     });
1325    
1326     $table
1327     }
1328    
1329     sub stats_window {
1330 root 1.18 my $r = new DC::UI::ScrolledWindow (
1331 root 1.1 expand => 1,
1332     scroll_y => 1
1333     );
1334 root 1.18 $r->add (my $vb = new DC::UI::VBox);
1335 root 1.1
1336 root 1.18 $vb->add (new DC::UI::FancyFrame
1337 root 1.1 label => "Player",
1338 root 1.18 child => (my $pi = new DC::UI::VBox),
1339 root 1.1 );
1340    
1341 root 1.22 $pi->add ($STATWIDS->{title} = new DC::UI::Label text => "Title:", expand => 1, align => 0,
1342 root 1.1 can_hover => 1, can_events => 1,
1343     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1344 root 1.22 $pi->add ($STATWIDS->{map} = new DC::UI::Label align => 0, text => "Map:", expand => 1,
1345 root 1.1 can_hover => 1, can_events => 1,
1346     tooltip => "The map you are currently on (if supported by the server).");
1347    
1348 root 1.18 $pi->add (my $hb0 = new DC::UI::HBox);
1349 root 1.22 $hb0->add ($STATWIDS->{weight} = new DC::UI::Label text => "Weight:", expand => 1, align => 0,
1350 root 1.1 can_hover => 1, can_events => 1,
1351     tooltip => "The weight of the player including all inventory items.");
1352 root 1.22 $hb0->add ($STATWIDS->{m_weight} = new DC::UI::Label align => 0, text => "Max weight:", expand => 1,
1353 root 1.1 can_hover => 1, can_events => 1,
1354     tooltip => "The weight limit: you cannot carry more than this.");
1355    
1356 root 1.18 $vb->add (new DC::UI::FancyFrame
1357 root 1.1 label => "Primary/Secondary Statistics",
1358 root 1.18 child => (my $hb = new DC::UI::HBox expand => 1),
1359 root 1.1 );
1360 root 1.18 $hb->add (my $tbl = new DC::UI::Table expand => 1);
1361 root 1.1
1362     my $color2 = [1, 1, 0];
1363    
1364     for (
1365     [0, 0, st_str => "Str", 30],
1366     [0, 1, st_dex => "Dex", 30],
1367     [0, 2, st_con => "Con", 30],
1368     [0, 3, st_int => "Int", 30],
1369     [0, 4, st_wis => "Wis", 30],
1370     [0, 5, st_pow => "Pow", 30],
1371     [0, 6, st_cha => "Cha", 30],
1372    
1373     [2, 0, st_wc => "Wc", -120],
1374     [2, 1, st_ac => "Ac", -120],
1375     [2, 2, st_dam => "Dam", 120],
1376     [2, 3, st_arm => "Arm", 120],
1377     [2, 4, st_spd => "Spd", 10.54],
1378     [2, 5, st_wspd => "WSp", 10.54],
1379     ) {
1380     my ($col, $row, $id, $label, $template) = @$_;
1381    
1382 root 1.18 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new DC::UI::Label
1383 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1,
1384     align => 1, template => $template, tooltip => "#stat_$label");
1385 root 1.18 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new DC::UI::Label
1386 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2,
1387     align => 0, text => $label, tooltip => "#stat_$label");
1388 root 1.1 }
1389    
1390 root 1.18 $vb->add (new DC::UI::FancyFrame
1391 root 1.1 label => "Resistancies",
1392 root 1.22 child => (my $tbl2 = new DC::UI::Table expand => 1, col_expand => [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0]),
1393 root 1.1 );
1394    
1395     my $row = 0;
1396     my $col = 0;
1397    
1398     my %resist_names = (
1399     slow => ["Slow",
1400     "<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.)"],
1401     holyw => ["Holy Word",
1402     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1403     conf => ["Confusion",
1404     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1405     fire => ["Fire",
1406     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1407     depl => ["Depletion",
1408     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1409     magic => ["Magic",
1410     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1411     drain => ["Draining",
1412     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1413     acid => ["Acid",
1414     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1415     pois => ["Poison",
1416     "<b>Poison</b> (resistance to getting poisoned)"],
1417     para => ["Paralysation",
1418     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1419     deat => ["Death",
1420     "<b>Death</b> (resistance against death spells)"],
1421     phys => ["Physical",
1422     "<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.)"],
1423     blind => ["Blind",
1424     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1425     fear => ["Fear",
1426     "<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)"],
1427     tund => ["Turn undead",
1428     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1429     elec => ["Electricity",
1430     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1431     cold => ["Cold",
1432     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1433     ghit => ["Ghost hit",
1434     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1435     );
1436    
1437     for (qw/slow holyw conf fire depl magic
1438     drain acid pois para deat phys
1439     blind fear tund elec cold ghit/)
1440     {
1441 root 1.22 $tbl2->add_at ($col + 2, $row,
1442 root 1.1 $STATWIDS->{"res_$_"} =
1443 root 1.18 new DC::UI::Label
1444 root 1.1 font => $FONT_FIXED,
1445     template => "-100%",
1446 root 1.22 align => 1,
1447 root 1.1 can_events => 1,
1448     can_hover => 1,
1449     tooltip => $resist_names{$_}->[1],
1450     );
1451 root 1.18 $tbl2->add_at ($col + 1, $row, new DC::UI::Image
1452 root 1.1 font => $FONT_FIXED,
1453     can_hover => 1,
1454     can_events => 1,
1455     path => "ui/resist/resist_$_.png",
1456     tooltip => $resist_names{$_}->[1],
1457     );
1458 root 1.22 $tbl2->add_at ($col + 0, $row, new DC::UI::Label
1459 root 1.1 text => $resist_names{$_}->[0],
1460     font => $FONT_FIXED,
1461 root 1.22 align => 1,
1462 root 1.1 can_hover => 1,
1463     can_events => 1,
1464     tooltip => $resist_names{$_}->[1],
1465     );
1466    
1467     $row++;
1468     if ($row % 6 == 0) {
1469 root 1.22 $col += 4;
1470 root 1.1 $row = 0;
1471     }
1472     }
1473    
1474     #update_stats_window ({});
1475    
1476     $r
1477     }
1478    
1479     sub skill_window {
1480 root 1.18 my $sw = new DC::UI::ScrolledWindow (expand => 1);
1481 root 1.1
1482 root 1.18 $sw->add ($STATWIDS->{skill_tbl} = new DC::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1483 root 1.1
1484     $sw
1485     }
1486    
1487     sub formsep($) {
1488     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1489     }
1490    
1491     my $METASERVER_ATIME;
1492    
1493     sub update_metaserver {
1494     my ($metaserver_dialog) = @_;
1495    
1496     $METASERVER = $metaserver_dialog
1497     if defined $metaserver_dialog;
1498    
1499     return if $METASERVER_ATIME > time;
1500     $METASERVER_ATIME = time + 60;
1501    
1502     my $table = $METASERVER->{table};
1503     $table->clear;
1504 root 1.18 $table->add_at (0, 0, my $label = new DC::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1505 root 1.1
1506     my $ok = 0;
1507    
1508 root 1.18 DC::background {
1509     my $ua = DC::lwp_useragent;
1510 root 1.1
1511 root 1.18 DC::background_msg DC::decode_json +(DC::lwp_check $ua->get ($META_SERVER))->decoded_content;
1512 root 1.1 } sub {
1513     my ($msg) = @_;
1514     if ($msg) {
1515     $table->clear;
1516    
1517     my @tip = (
1518     "The current number of users logged in on the server.",
1519     "The hostname of the server.",
1520     "The time this server has been running without being restarted.",
1521     "Short information about this server provided by its admins.",
1522     );
1523     my @col = qw(#Users Host Uptime Version Description);
1524 root 1.18 $table->add_at ($_, 0, new DC::UI::Label
1525 root 1.22 can_hover => 1, can_events => 1, fg => [1, 1, 0],
1526 root 1.1 text => $col[$_], tooltip => $tip[$_])
1527     for 0 .. $#col;
1528    
1529 root 1.22 my @align = qw(1 0.5 1 1 0);
1530 root 1.1
1531     my $y = 0;
1532     for my $m (@{ $msg->{servers} }) {
1533     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1534     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1535    
1536     for ($desc) {
1537     s/<br>/\n/gi;
1538     s/<li>/\n· /gi;
1539     s/<.*?>//sgi;
1540     s/&amp;/&/g;
1541     s/&lt;/</g;
1542     s/&gt;/>/g;
1543     }
1544    
1545     $uptime = sprintf "%dd %02d:%02d:%02d",
1546     (int $uptime / 86400),
1547     (int $uptime / 3600) % 24,
1548     (int $uptime / 60) % 60,
1549     $uptime % 60;
1550    
1551     $m = [$users, $host, $uptime, $version, $desc];
1552    
1553     $y++;
1554    
1555 root 1.18 $table->add_at (scalar @$m, $y, new DC::UI::VBox children => [
1556     (new DC::UI::Button
1557 root 1.1 text => "Use",
1558     tooltip => "Put this server into the <b>Host:Port</b> field",
1559     on_activate => sub {
1560     $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1561     $METASERVER->hide;
1562     0
1563     },
1564     ),
1565 root 1.18 (new DC::UI::Empty expand => 1),
1566 root 1.1 ]);
1567    
1568 root 1.18 $table->add_at ($_, $y, new DC::UI::Label
1569 root 1.1 max_w => $::WIDTH * 0.4,
1570     ellipsise => 0,
1571     align => $align[$_],
1572     text => $m->[$_],
1573     tooltip => $tip[$_],
1574     fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1575     can_hover => 1,
1576     can_events => 1,
1577     fontsize => 0.8)
1578     for 0 .. $#$m;
1579     }
1580     } else {
1581     $ok or $label->set_text ("error while contacting metaserver");
1582     }
1583     };
1584    
1585     }
1586    
1587     sub metaserver_dialog {
1588 root 1.18 my $vbox = new DC::UI::VBox;
1589     my $table = new DC::UI::Table;
1590     $vbox->add (new DC::UI::ScrolledWindow expand => 1, child => $table);
1591 root 1.1
1592 root 1.18 my $dialog = new DC::UI::Toplevel
1593 root 1.1 title => "Server List",
1594     name => 'metaserver_dialog',
1595     x => 'center',
1596     y => 'center',
1597     z => 3,
1598     force_w => $::WIDTH * 0.9,
1599     force_h => $::HEIGHT * 0.7,
1600     child => $vbox,
1601     has_close_button => 1,
1602     table => $table,
1603     on_visibility_change => sub {
1604     update_metaserver ($_[0]) if $_[1];
1605     0
1606     },
1607     ;
1608    
1609     $dialog
1610     }
1611    
1612     sub login_setup {
1613 root 1.18 my $vbox = new DC::UI::VBox;
1614 root 1.1
1615 root 1.18 $vbox->add (new DC::UI::FancyFrame
1616 root 1.1 label => "Login Settings",
1617 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1618 root 1.1 );
1619    
1620 root 1.22 $table->add_at (0, 4, new DC::UI::Label align => 1, text => "Username");
1621 root 1.18 $table->add_at (1, 4, new DC::UI::Entry
1622 root 1.1 text => $CFG->{profile}{default}{user},
1623     tooltip => "The name of your character on the server.",
1624     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1625     );
1626    
1627 root 1.22 $table->add_at (0, 5, new DC::UI::Label align => 1, text => "Password");
1628 root 1.18 $table->add_at (1, 5, new DC::UI::Entry
1629 root 1.1 text => $CFG->{profile}{default}{password},
1630     hidden => 1,
1631     tooltip => "The password for your character.",
1632     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1633     );
1634    
1635 root 1.18 $table->add_at (1, 11, $LOGIN_BUTTON = new DC::UI::Button
1636 root 1.1 expand => 1,
1637     text => "Login / Register",
1638     tooltip => "This button will either login to the account configured above or register a new account.",
1639     on_activate => sub {
1640     $CONN ? stop_game
1641     : start_game;
1642     1
1643     },
1644     );
1645    
1646 root 1.18 $vbox->add (new DC::UI::FancyFrame
1647 root 1.61 label => "How to Play",
1648 root 1.65 min_h => 240,
1649 root 1.22 child => (new DC::UI::Label valign => 0, ellipsise => 0,
1650 root 1.1 markup =>
1651 root 1.63 "First select a suitable video resolution in the <b>Graphics</b> tab, above.\n\n"
1652 root 1.62 . "Then register a new account (or use an existing one if you have one). "
1653     . "To register an account, choose a username that hasn't been taken yet (just guess) and "
1654 root 1.1 . "try to log-in. Follow the instructions in the Log tab in the message window.",
1655     ),
1656     );
1657    
1658     $vbox
1659     }
1660    
1661     sub server_setup {
1662 root 1.18 my $vbox = new DC::UI::VBox;
1663 root 1.1
1664 root 1.18 $vbox->add (new DC::UI::FancyFrame
1665 root 1.1 label => "Connection Settings",
1666 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1667 root 1.1 );
1668    
1669     my $row = 0;
1670    
1671 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Host:Port");
1672 root 1.1 {
1673 root 1.18 $table->add_at (1, $row, my $vbox = new DC::UI::VBox);
1674 root 1.1
1675     $vbox->add (
1676 root 1.18 $HOST_ENTRY = new DC::UI::Entry
1677 root 1.1 expand => 1,
1678     text => $CFG->{profile}{default}{host},
1679 root 1.28 tooltip => "The hostname or ip address of the Deliantra server to connect to (e.g. <b>gameserver.deliantra.net</b>)",
1680 root 1.1 on_changed => sub {
1681     my ($self, $value) = @_;
1682     $CFG->{profile}{default}{host} = $value;
1683     1
1684     }
1685     );
1686    
1687 root 1.5 if (0) { #d# disabled
1688 root 1.18 $vbox->add (new DC::UI::Button
1689 root 1.1 expand => 1,
1690     text => "Server List",
1691     other => $METASERVER,
1692 root 1.28 tooltip => "Show a list of available Deliantra servers",
1693 root 1.1 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1694     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1695     );
1696 root 1.5 }#d#
1697 root 1.1 }
1698    
1699 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Map Size");
1700 root 1.18 $table->add_at (1, $row, new DC::UI::Slider
1701 root 1.1 force_w => 100,
1702     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1703     tooltip => "This is the size of the portion of the map update the server sends you. "
1704     . "If you set this to a high value you will be able to see further, "
1705     . "but you also increase bandwidth requirements and latency. "
1706     . "This option is only used once at log-in.",
1707     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1708     );
1709    
1710 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Output-Rate");
1711 root 1.18 $table->add_at (1, $row, new DC::UI::Entry
1712 root 1.1 text => $CFG->{output_rate},
1713     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1714     . "when sending data. When 0 or unset, the server "
1715     . "default will be used, which is usually around 100kb/s. Most servers will "
1716     . "dynamically find an optimal rate, so adjust this only when necessary.",
1717     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1718     );
1719    
1720 root 1.18 $vbox->add (new DC::UI::FancyFrame
1721 root 1.1 label => "Server Info",
1722 root 1.18 child => ($SERVER_INFO = new DC::UI::Label ellipsise => 0),
1723 root 1.1 );
1724    
1725     $vbox
1726     }
1727    
1728     sub client_setup {
1729 root 1.18 my $table = new DC::UI::Table expand => 1, col_expand => [0, 1];
1730 root 1.1
1731     my $row = 0;
1732    
1733 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Tip of the day");
1734 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1735 root 1.1 state => $CFG->{show_tips},
1736     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1737     on_changed => sub {
1738     my ($self, $value) = @_;
1739     $CFG->{show_tips} = $value;
1740     0
1741     }
1742     );
1743    
1744 root 1.37 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Window Size");
1745 root 1.18 $table->add_at (1, $row++, my $saycmd = new DC::UI::Entry
1746 root 1.1 text => $CFG->{logview_max_par},
1747 root 1.37 tooltip => "This is maximum number of messages remembered in the <b>Message</b> window. If the server "
1748 root 1.1 . "sends more messages than this number, older messages get removed to save memory and "
1749     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1750     on_changed => sub {
1751     my ($self, $value) = @_;
1752 root 1.28 $MESSAGE_DIST->set_max_par ($CFG->{logview_max_par} = $value*1);
1753 root 1.1 0
1754     },
1755     );
1756    
1757     $table
1758     }
1759    
1760     sub autopickup_setup {
1761 root 1.18 my $r = new DC::UI::ScrolledWindow (
1762 root 1.1 expand => 1,
1763     scroll_y => 1
1764     );
1765 root 1.18 $r->add (my $table = new DC::UI::Table
1766 root 1.1 row_expand => [0],
1767     col_expand => [0, 1, 0, 1],
1768     );
1769    
1770     for (
1771     ["General", 0, 0,
1772     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1773     ["Inhibit autopickup" => PICKUP_INHIBIT],
1774     ["Stop before pickup" => PICKUP_STOP],
1775     ["Debug autopickup" => PICKUP_DEBUG],
1776     ],
1777     ["Weapons", 0, 6,
1778     ["All weapons" => PICKUP_ALLWEAPON],
1779     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1780     ["Bows" => PICKUP_BOW],
1781     ["Arrows" => PICKUP_ARROW],
1782     ],
1783     ["Armour", 0, 12,
1784     ["Helmets" => PICKUP_HELMET],
1785     ["Shields" => PICKUP_SHIELD],
1786     ["Body Armour" => PICKUP_ARMOUR],
1787     ["Boots" => PICKUP_BOOTS],
1788     ["Gloves" => PICKUP_GLOVES],
1789     ["Cloaks" => PICKUP_CLOAK],
1790     ],
1791    
1792     ["Readables", 2, 0,
1793     ["Spellbooks" => PICKUP_SPELLBOOK],
1794     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1795     ["Normal Books/Scrolls" => PICKUP_READABLES],
1796     ],
1797     ["Misc", 2, 5,
1798     ["Food" => PICKUP_FOOD],
1799     ["Drinks" => PICKUP_DRINK],
1800     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1801     ["Keys" => PICKUP_KEY],
1802     ["Magical Items" => PICKUP_MAGICAL],
1803     ["Potions" => PICKUP_POTION],
1804     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1805     ["Ignore cursed" => PICKUP_NOT_CURSED],
1806     ["Jewelery" => PICKUP_JEWELS],
1807     ["Flesh" => PICKUP_FLESH],
1808     ],
1809 root 1.58 ["Value/Weight ratio", 2, 17]
1810 root 1.1 )
1811     {
1812     my ($title, $x, $y, @bits) = @$_;
1813 root 1.18 $table->add_at ($x, $y, new DC::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1814 root 1.1
1815     for (@bits) {
1816     ++$y;
1817    
1818     my $mask = $_->[1];
1819 root 1.18 $table->add_at ($x , $y, new DC::UI::Label text => $_->[0], align => 1, expand => 1);
1820     $table->add_at ($x+1, $y, my $checkbox = new DC::UI::CheckBox
1821 root 1.1 state => $::CFG->{pickup} & $mask,
1822     on_changed => sub {
1823     my ($box, $value) = @_;
1824    
1825     if ($value) {
1826     $::CFG->{pickup} |= $mask;
1827     } else {
1828     $::CFG->{pickup} &= ~$mask;
1829     }
1830    
1831     $::CONN->send_command ("pickup $::CFG->{pickup}")
1832     if defined $::CONN;
1833    
1834     0
1835     });
1836    
1837     ${$_->[2]} = $checkbox if $_->[2];
1838     }
1839     }
1840    
1841 root 1.18 $table->add_at (2, 18, new DC::UI::ValSlider
1842 root 1.1 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1843     template => ">= 99",
1844 root 1.58 tooltip => "Pick up items whose value/weight (silver/kg) ratio is equal or higher than this setting (which is specified in gold coins).",
1845 root 1.1 to_value => sub { ">= " . 5 * $_[0] },
1846     on_changed => sub {
1847     my ($slider, $value) = @_;
1848    
1849     $::CFG->{pickup} &= ~0xF;
1850     $::CFG->{pickup} |= int $value
1851     if $value;
1852     1;
1853     });
1854    
1855 root 1.18 $table->add_at (3, 18, new DC::UI::Button
1856 root 1.1 text => "set",
1857     on_activate => sub {
1858     $::CONN->send_command ("pickup $::CFG->{pickup}")
1859     if defined $::CONN;
1860     0
1861     });
1862    
1863     $r
1864     }
1865    
1866     my %SORT_ORDER = (
1867 elmex 1.41 type => sub {
1868     sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @_
1869     },
1870 root 1.1 mtime => sub {
1871     my $NOW = time;
1872     sort {
1873     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1874     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1875    
1876     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1877     or $btime <=> $atime
1878     or $a->{type} <=> $b->{type}
1879     } @_
1880     },
1881     weight => sub { sort {
1882     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1883     or $a->{type} <=> $b->{type}
1884     } @_ },
1885     );
1886    
1887     sub inventory_widget {
1888 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1889 root 1.1
1890 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1891 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1892 root 1.1
1893 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1894 root 1.1
1895     use sort 'stable';
1896    
1897 root 1.18 $hb1->add (new DC::UI::Selector
1898 root 1.1 value => $::CFG->{inv_sort},
1899     options => [
1900     [type => "Type/Name"],
1901     [mtime => "Recent/Normal/Locked"],
1902     [weight => "Weight/Type"],
1903     ],
1904     on_changed => sub {
1905     $::CFG->{inv_sort} = $_[1];
1906     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1907     },
1908     );
1909 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1910 root 1.58 #TODO# update to weight/maxweight
1911 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1912 root 1.1
1913 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1914     $sw1->add ($INV = new DC::UI::Inventory);
1915 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1916    
1917 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1918 root 1.1
1919 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1920 root 1.1
1921 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1922     $sw2->add ($INVR = new DC::UI::Inventory);
1923 root 1.1
1924     # XXX: Call after $INVR = ... because set_opencont sets the items
1925 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
1926 root 1.1
1927     $hb
1928     }
1929    
1930     sub media_window {
1931 root 1.18 my $vb = new DC::UI::VBox;
1932 root 1.1
1933 root 1.18 $vb->add (new DC::UI::FancyFrame
1934 root 1.1 label => "Currently playing music",
1935 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1936     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
1937 root 1.1 );
1938    
1939 root 1.18 $vb->add (new DC::UI::FancyFrame
1940 root 1.1 label => "Other media used in this session",
1941     expand => 1,
1942 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
1943 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1944     );
1945    
1946     $vb
1947     }
1948    
1949     sub add_license {
1950     my ($meta) = @_;
1951    
1952     $meta = $meta->{data}
1953     or return;
1954    
1955     $meta->{license} || $meta->{author} || $meta->{source}
1956     or return;
1957    
1958     $LICENSE_WIDGET->add_paragraph ({
1959     fg => [1, 1, 1, 1],
1960     markup => "<small>"
1961 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
1962     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
1963     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
1964     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
1965 root 1.1 . "</small>",
1966     });
1967     $LICENSE_WIDGET->scroll_to_bottom;
1968     }
1969    
1970     sub toggle_player_page {
1971     my ($widget) = @_;
1972    
1973     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1974     $PL_WINDOW->hide;
1975     } else {
1976     $PL_NOTEBOOK->set_current_page ($widget);
1977     $PL_WINDOW->show;
1978     }
1979     }
1980    
1981     sub player_window {
1982 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
1983 root 1.1 x => "center",
1984     y => "center",
1985     force_w => $WIDTH * 9/10,
1986     force_h => $HEIGHT * 9/10,
1987     title => "Player",
1988     name => "playerbook",
1989     has_close_button => 1
1990     ;
1991    
1992     my $ntb =
1993     $PL_NOTEBOOK =
1994 root 1.18 new DC::UI::Notebook expand => 1;
1995 root 1.1
1996     $ntb->add_tab (
1997     "Statistics (F2)" => $STATS_PAGE = stats_window,
1998     "Shows statistics, where all your Stats and Resistances are shown."
1999     );
2000     $ntb->add_tab (
2001     "Skills (F3)" => $SKILL_PAGE = skill_window,
2002     "Shows all your Skills."
2003     );
2004    
2005 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
2006     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
2007 root 1.1 $ntb->add_tab (
2008     "Spellbook (F4)" => $spellsw,
2009     "Displays all spells you have and lets you edit keyboard shortcuts for them."
2010     );
2011     $ntb->add_tab (
2012     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
2013     "Toggles the inventory window, where you can manage your loot (or treasures :). "
2014     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
2015     );
2016     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
2017     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
2018    
2019     $ntb->add_tab (Media => media_window,
2020     "License, Author and Source info for media sent by the server.");
2021    
2022     $ntb->set_current_page ($INVENTORY_PAGE);
2023    
2024     $plwin->add ($ntb);
2025     $plwin
2026     }
2027    
2028     sub keyboard_setup {
2029 root 1.18 DC::Macro::keyboard_setup
2030 root 1.1 }
2031    
2032     sub help_window {
2033 root 1.18 my $win = new DC::UI::Toplevel
2034 root 1.1 x => 'center',
2035     y => 'center',
2036     z => 4,
2037     name => 'doc_browser',
2038     force_w => int $WIDTH * 7/8,
2039     force_h => int $HEIGHT * 7/8,
2040     title => "Help Browser",
2041     has_close_button => 1;
2042    
2043 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
2044 root 1.1
2045 root 1.18 $vbox->add (new DC::UI::FancyFrame
2046 root 1.1 label => "Navigation",
2047 root 1.18 child => (my $buttons = new DC::UI::HBox),
2048 root 1.1 );
2049 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2050 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2051    
2052     my @history;
2053     my @future;
2054     my $curnode;
2055    
2056     my $load_node; $load_node = sub {
2057     my ($node, $para) = @_;
2058    
2059     $buttons->clear;
2060    
2061 root 1.18 $buttons->add (new DC::UI::Button
2062 root 1.1 text => "⇤",
2063     tooltip => "back to the starting page",
2064     on_activate => sub {
2065     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2066     unshift @future, @history;
2067     @history = ();
2068     $load_node->(@{shift @future});
2069     },
2070     );
2071    
2072     if (@history) {
2073 root 1.18 $buttons->add (new DC::UI::Button
2074 root 1.1 text => "⋘",
2075 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
2076 root 1.1 on_activate => sub {
2077     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2078     $load_node->(@{pop @history});
2079     },
2080     );
2081     }
2082    
2083     if (@future) {
2084 root 1.18 $buttons->add (new DC::UI::Button
2085 root 1.1 text => "⋙",
2086 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
2087 root 1.1 on_activate => sub {
2088     push @history, [$curnode, $viewer->current_paragraph];
2089     $load_node->(@{shift @future});
2090     },
2091     );
2092     }
2093    
2094 root 1.18 $buttons->add (new DC::UI::Label text => " ");
2095 root 1.1
2096 root 1.18 my @path = DC::Pod::full_path_of $node;
2097 root 1.1 pop @path; # drop current node
2098    
2099     for my $node (@path) {
2100 root 1.18 $buttons->add (new DC::UI::Button
2101 root 1.31 text => $node->[DC::Pod::N_KW][0],
2102 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
2103 root 1.1 on_activate => sub {
2104     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2105     $load_node->($node);
2106     },
2107     );
2108 root 1.18 $buttons->add (new DC::UI::Label text => "/");
2109 root 1.1 }
2110    
2111 root 1.31 $buttons->add (new DC::UI::Label text => $node->[DC::Pod::N_KW][0], padding_x => 4, padding_y => 4);
2112 root 1.1
2113     $curnode = $node;
2114    
2115     $viewer->clear;
2116 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
2117 root 1.1 $viewer->scroll_to ($para);
2118     };
2119    
2120 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
2121 root 1.1
2122 root 1.18 $DC::Pod::goto_document = sub {
2123 root 1.1 my (@path) = @_;
2124    
2125     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2126    
2127 root 1.18 $load_node->((DC::Pod::find @path)[0]);
2128 root 1.1 $win->show;
2129     };
2130    
2131     $win
2132     }
2133    
2134     sub open_string_query {
2135     my ($title, $cb, $txt, $tooltip) = @_;
2136 root 1.18 my $dialog = new DC::UI::Toplevel
2137 root 1.1 x => "center",
2138     y => "center",
2139     z => 50,
2140     force_w => $WIDTH * 4/5,
2141     title => $title;
2142    
2143     $dialog->add (
2144 root 1.18 my $e = new DC::UI::Entry
2145 root 1.1 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2146     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2147     tooltip => $tooltip
2148     );
2149    
2150     $e->grab_focus;
2151     $e->set_text ($txt) if $txt;
2152     $dialog->show;
2153     }
2154    
2155     sub open_quit_dialog {
2156     unless ($QUIT_DIALOG) {
2157 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2158 root 1.1 x => "center",
2159     y => "center",
2160     z => 50,
2161     title => "Really Quit?",
2162     on_key_down => sub {
2163     my ($dialog, $ev) = @_;
2164     $ev->{sym} == 27 and $dialog->hide;
2165     }
2166     ;
2167    
2168 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2169 root 1.1
2170 root 1.18 $vb->add (new DC::UI::Label
2171 root 1.1 text => "You should find a savebed and apply it first!",
2172     max_w => $WIDTH * 0.25,
2173     ellipsize => 0,
2174     );
2175 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2176     $hb->add (new DC::UI::Button
2177 root 1.1 text => "Ok",
2178     expand => 1,
2179     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2180     );
2181 root 1.18 $hb->add (new DC::UI::Button
2182 root 1.1 text => "Quit anyway",
2183     expand => 1,
2184 root 1.66 on_activate => sub {
2185     crash "Quit anyway";
2186     EV::unloop EV::UNLOOP_ALL;
2187     },
2188 root 1.1 );
2189     }
2190    
2191     $QUIT_DIALOG->show;
2192     $QUIT_DIALOG->grab_focus;
2193     }
2194    
2195     sub show_tip_of_the_day {
2196     # find all tips
2197 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2198 root 1.1
2199 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2200 root 1.1 my ($todindex) = @_;
2201     $todindex = 0 if $todindex >= @tod;
2202 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2203 root 1.1
2204     # create dialog
2205     my $dialog;
2206    
2207     my $close = sub {
2208     $dialog->destroy;
2209     };
2210    
2211 root 1.18 $dialog = new DC::UI::Toplevel
2212 root 1.1 x => "center",
2213     y => "center",
2214     z => 3,
2215     name => 'tip_of_the_day',
2216     force_w => int $WIDTH * 4/9,
2217     force_h => int $WIDTH * 2/9,
2218     title => "Tip of the day #" . (1 + $todindex),
2219 root 1.18 child => my $vbox = new DC::UI::VBox,
2220 root 1.1 has_close_button => 1,
2221     on_delete => $close,
2222     ;
2223    
2224 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2225 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2226 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2227 root 1.1
2228 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2229 root 1.1
2230 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2231 root 1.1 text => "Close",
2232     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>.",
2233     on_activate => $close,
2234     );
2235    
2236 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2237 root 1.1 text => "Next",
2238     tooltip => "Show the next <b>Tip of the day</b>.",
2239     on_activate => sub {
2240     $close->();
2241     &show_tip_of_the_day;
2242     },
2243     );
2244    
2245     $dialog->show;
2246     };
2247     }
2248    
2249     sub sdl_init {
2250 root 1.52 DC::SDL_Init DC::SDL_INIT_AUDIO #| DC::SDL_NOPARACHUTE
2251 root 1.1 and die "SDL::Init failed!\n";
2252     }
2253    
2254     sub video_init {
2255 root 1.52 DC::SDL_InitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2256     $SDL_REINIT = 0;
2257    
2258     @SDL_MODES = DC::SDL_ListModes 8, $CFG->{disable_alpha} ? 0 : 8;
2259     @SDL_MODES = DC::SDL_ListModes 8, 8 unless @SDL_MODES;
2260     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2261     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2262    
2263     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2264    
2265 root 1.61 if (!defined $CFG->{sdl_mode} or $CFG->{sdl_mode} > $#SDL_MODES) {
2266     $CFG->{sdl_mode} = 0; # lowest resolution by default
2267    
2268     # now choose biggets mode <= 1024x768
2269     for (0 .. $#SDL_MODES) {
2270     if ($SDL_MODES[$_][0] * $SDL_MODES[$_][1] <= 1024 * 768) {
2271     $CFG->{sdl_mode} = $_;
2272     }
2273 root 1.52 }
2274     }
2275 root 1.1
2276     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2277    
2278     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2279     $FULLSCREEN = $CFG->{fullscreen};
2280     $FAST = $CFG->{fast};
2281    
2282 root 1.59 # due to mac os x braindamage, we simply retry with !fullscreen in case of an error
2283 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2284 root 1.59 or DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, !$FULLSCREEN
2285 root 1.18 or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2286 root 1.1
2287     $SDL_ACTIVE = 1;
2288     $LAST_REFRESH = time - 0.01;
2289    
2290 root 1.18 DC::OpenGL::init;
2291     DC::Macro::init;
2292 root 1.1
2293     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2294    
2295 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2296 root 1.1
2297     #############################################################################
2298    
2299     if ($DEBUG_STATUS) {
2300 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2301 root 1.1 } else {
2302     # create/configure the widgets
2303    
2304 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2305 root 1.1 my (undef, $ev) = @_;
2306    
2307 root 1.18 if (my @macros = DC::Macro::find $ev) {
2308     DC::Macro::execute $_ for @macros;
2309 root 1.1
2310     return 1;
2311     }
2312    
2313     0
2314     });
2315    
2316 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2317 root 1.1 padding => 0,
2318     z => 100,
2319     force_x => "max",
2320     force_y => 0;
2321     $DEBUG_STATUS->show;
2322    
2323 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2324 root 1.29
2325     $MODBOX = new DC::UI::Label
2326     can_events => 1,
2327     can_hover => 1,
2328     markup => "",
2329     align => 0,
2330     font => $FONT_FIXED,
2331 root 1.30 tooltip => "#modifier_box",
2332     tooltip_width => 0.67,
2333     ;
2334 root 1.29
2335     update_modbox;
2336 root 1.1
2337 root 1.18 (new DC::UI::Frame
2338 root 1.1 bg => [0, 0, 0, 0.4],
2339     force_x => 0,
2340     force_y => "max",
2341 root 1.29 child => (my $LR = new DC::UI::VBox),
2342 root 1.1 )->show;
2343    
2344 root 1.29 $LR->add ($STATUSBOX);
2345     $LR->add ($MODBOX);
2346     $LR->add (new DC::UI::Label
2347     align => 0,
2348     markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode",
2349     fontsize => 0.5,
2350     fg => [1, 1, 0, 0.7],
2351     );
2352    
2353 root 1.18 DC::UI::Toplevel->new (
2354 root 1.2 title => "Minimap",
2355 root 1.1 name => "mapmap",
2356     x => 0,
2357     y => $FONTSIZE + 8,
2358     border_bg => [1, 1, 1, 192/255],
2359     bg => [1, 1, 1, 0],
2360 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2361 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2362     ),
2363     )->show;
2364    
2365 root 1.18 $MAPWIDGET = new DC::MapWidget;
2366 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2367     my ($mapwidget, $preset) = @_;
2368    
2369 elmex 1.23 $MESSAGE_DIST->activate_console ($preset)
2370     if $MESSAGE_DIST;
2371 root 1.1 });
2372     $MAPWIDGET->show;
2373     $MAPWIDGET->grab_focus;
2374    
2375 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2376 root 1.1 command => { },
2377     tooltip => "#completer_help",
2378     ;
2379    
2380 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2381 root 1.1 title => "Setup",
2382     name => "setup_dialog",
2383     x => 'center',
2384     y => 'center',
2385     z => 2,
2386     force_w => $::WIDTH * 0.6,
2387     force_h => $::HEIGHT * 0.6,
2388     has_close_button => 1,
2389     ;
2390    
2391     $METASERVER = metaserver_dialog;
2392 root 1.39 # the name is changed to not conflict with the older name as users could have hidden it
2393 root 1.40 $MESSAGE_WINDOW = new DC::UI::Dockbar
2394     name => "message_window2",
2395     title => 'Messages',
2396     force_w => $::WIDTH * 0.6,
2397     force_h => $::HEIGHT * 0.25,
2398     ;
2399    
2400 elmex 1.23 $MESSAGE_DIST = new DC::MessageDistributor dockbar => $MESSAGE_WINDOW;
2401 root 1.1
2402 root 1.38 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1,
2403 root 1.18 filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2404 root 1.1
2405     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2406     "Configure the server to play on, your username and password.");
2407     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2408     "Configure other server related options.");
2409     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2410     "Configure various client-specific settings.");
2411     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2412     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2413     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2414     "Configure the use of audio, sound effects and background music.");
2415     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2416     "Lets you define, edit and delete key bindings."
2417     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2418     . "with nothing set and the recording started. After doing the actions you "
2419     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2420     . "After pressing the combo the binding will be saved automatically and the "
2421     . "binding editor closes");
2422     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2423     "Some debuggin' options. Do not ask.");
2424    
2425 root 1.18 $BUTTONBAR = new DC::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2426 root 1.1
2427 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2428 root 1.1 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2429    
2430 root 1.37 # $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2431     # tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2432 root 1.1
2433     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
2434    
2435 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Playerbook", other => player_window,
2436 root 1.1 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2437    
2438 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2439 root 1.1 text => "Save Config",
2440     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2441     on_activate => sub {
2442 root 1.18 $::CFG->{layout} = DC::UI::get_layout;
2443 root 1.35 DC::write_cfg;
2444 root 1.1 status "Configuration Saved";
2445     0
2446     },
2447     );
2448    
2449 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2450 root 1.1 tooltip => "View Documentation");
2451    
2452 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2453 root 1.1 text => "Quit",
2454     tooltip => "Terminates the program",
2455     on_activate => sub {
2456     if ($CONN) {
2457     open_quit_dialog;
2458     } else {
2459 root 1.4 EV::unloop EV::UNLOOP_ALL;
2460 root 1.1 }
2461     0
2462     },
2463     );
2464    
2465     $BUTTONBAR->show;
2466     $SETUP_DIALOG->show;
2467     $MESSAGE_WINDOW->show;
2468     }
2469    
2470 root 1.53 $MODE_SLIDER->set_range ([$CFG->{sdl_mode}, 0, $#SDL_MODES, 1, 1]);
2471     $MODE_SLIDER->emit (changed => $CFG->{sdl_mode});
2472    
2473     $CAVEAT_LABEL->set_text ("None :)");
2474 root 1.55 $CAVEAT_LABEL->set_text ("Software Rendering (very slow)")
2475 root 1.53 unless DC::SDL_GL_GetAttribute DC::SDL_GL_ACCELERATED_VISUAL;
2476    
2477 root 1.1 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2478     }
2479    
2480     sub video_shutdown {
2481 root 1.18 DC::OpenGL::shutdown;
2482 root 1.52 DC::SDL_QuitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2483 root 1.1
2484     undef $SDL_ACTIVE;
2485     }
2486    
2487     my %animate_object;
2488     my $animate_timer;
2489    
2490     my $fps = 9;
2491    
2492     sub force_refresh {
2493     if ($ENV{CFPLUS_DEBUG} & 4) {
2494     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2495     debug sprintf "%3.2f", $fps;
2496     }
2497    
2498 root 1.16 undef $WANT_REFRESH;
2499     $_[0]->stop;
2500 root 1.12
2501 root 1.18 $DC::UI::ROOT->draw;
2502     DC::SDL_GL_SwapBuffers;
2503 root 1.1 $LAST_REFRESH = $NOW;
2504     }
2505    
2506 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2507 root 1.1
2508 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2509     $NOW = EV::now;
2510 root 1.1
2511     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2512 root 1.18 for DC::poll_events;
2513 root 1.1
2514     if (%animate_object) {
2515     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2516 root 1.16 $WANT_REFRESH = 1;
2517 root 1.1 }
2518 root 1.16
2519     $want_refresh->start
2520     if $WANT_REFRESH;
2521 root 1.4 };
2522 root 1.1
2523     sub animation_start {
2524     my ($widget) = @_;
2525     $animate_object{$widget} = $widget;
2526     }
2527    
2528     sub animation_stop {
2529     my ($widget) = @_;
2530     delete $animate_object{$widget};
2531     }
2532    
2533     %SDL_CB = (
2534 root 1.18 DC::SDL_QUIT => sub {
2535 root 1.66 crash "SDL_QUIT";
2536 root 1.4 EV::unloop EV::UNLOOP_ALL;
2537 root 1.1 },
2538 root 1.18 DC::SDL_VIDEORESIZE => sub {
2539 root 1.1 },
2540 root 1.18 DC::SDL_VIDEOEXPOSE => sub {
2541     DC::UI::full_refresh;
2542 root 1.1 },
2543 root 1.18 DC::SDL_ACTIVEEVENT => sub {
2544 root 1.12 # not useful, as APPACTIVE includes only iconified state, not unmapped
2545 root 1.18 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2546     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2547     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2548     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2549     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2550 root 1.1 },
2551 root 1.18 DC::SDL_KEYDOWN => sub {
2552     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2553 root 1.1 # alt-enter
2554 root 1.52 video_shutdown;
2555 root 1.1 $FULLSCREEN_ENABLE->toggle;
2556     video_init;
2557     } else {
2558 root 1.29 &DC::UI::feed_sdl_key_down_event;
2559 root 1.1 }
2560 root 1.29 update_modbox;
2561     },
2562     DC::SDL_KEYUP => sub {
2563     &DC::UI::feed_sdl_key_up_event;
2564     update_modbox;
2565 root 1.1 },
2566 root 1.18 DC::SDL_MOUSEMOTION => \&DC::UI::feed_sdl_motion_event,
2567     DC::SDL_MOUSEBUTTONDOWN => \&DC::UI::feed_sdl_button_down_event,
2568     DC::SDL_MOUSEBUTTONUP => \&DC::UI::feed_sdl_button_up_event,
2569     DC::SDL_USEREVENT => sub {
2570 root 1.1 if ($_[0]{code} == 1) {
2571     audio_channel_finished $_[0]{data1};
2572     } elsif ($_[0]{code} == 0) {
2573     audio_music_finished;
2574     }
2575     },
2576     );
2577    
2578     #############################################################################
2579    
2580 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2581     EV::unloop;
2582     #d# TODO calling exit here hangs the process in some futex
2583     };
2584 root 1.1
2585 root 1.59 # due to mac os x + sdl combined briandamage, we need this contortion
2586     sub main {
2587     {
2588     DC::Pod::load_docwiki DC::find_rcfile "docwiki.pst";
2589 root 1.34
2590 root 1.59 if (-e "$Deliantra::VARDIR/client.cf") {
2591     DC::read_cfg "$Deliantra::VARDIR/client.cf";
2592     } else {
2593     #TODO: compatibility cruft
2594     DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2595     print STDERR "INFO: used old configuration file\n";
2596     }
2597 root 1.15
2598 root 1.59 DC::DB::Server::run;
2599 root 1.35
2600 root 1.59 if ($CFG->{db_schema} < 1) {
2601     warn "INFO: upgrading database schema from 0 to 1, mapcache and tilecache will be lost\n";
2602     DC::DB::nuke_db;
2603     $CFG->{db_schema} = 1;
2604     DC::write_cfg;
2605     }
2606 root 1.35
2607 root 1.59 DC::DB::open_db;
2608 root 1.1
2609 root 1.59 DC::UI::set_layout ($::CFG->{layout});
2610 root 1.1
2611 root 1.59 my %DEF_CFG = (
2612 root 1.61 sdl_mode => undef,
2613 root 1.59 fullscreen => 1,
2614     fast => 0,
2615     force_opengl11 => undef,
2616     disable_alpha => 0,
2617     smooth_movement => 1,
2618     texture_compression => 1,
2619     map_scale => 1,
2620     fow_enable => 1,
2621     fow_intensity => 0,
2622     map_smoothing => 1,
2623     gui_fontsize => 1,
2624     log_fontsize => 0.7,
2625     gauge_fontsize => 1,
2626     gauge_size => 0.35,
2627     stat_fontsize => 0.7,
2628     mapsize => 100,
2629     audio_enable => 1,
2630     audio_hw_channels => 0,
2631     audio_hw_frequency => 0,
2632     audio_hw_chunksize => 0,
2633     audio_mix_channels => 8,
2634     effects_enable => 1,
2635     effects_volume => 1,
2636     bgm_enable => 1,
2637     bgm_volume => 0.5,
2638     output_rate => "",
2639     pickup => 0,
2640     inv_sort => "mtime",
2641     default => "profile", # default profile
2642     show_tips => 1,
2643     logview_max_par => 1000,
2644     shift_fire_stop => 0,
2645     );
2646    
2647     while (my ($k, $v) = each %DEF_CFG) {
2648     $CFG->{$k} = $v unless exists $CFG->{$k};
2649     }
2650 root 1.1
2651 root 1.59 $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2652     $PROFILE = $CFG->{profile}{default};
2653 root 1.1
2654 root 1.59 # convert old bindings (only default profile matters)
2655     if (my $bindings = delete $PROFILE->{bindings}) {
2656     while (my ($mod, $syms) = each %$bindings) {
2657     while (my ($sym, $cmds) = each %$syms) {
2658     push @{ $PROFILE->{macro} }, {
2659     accelkey => [$mod*1, $sym*1],
2660     action => $cmds,
2661     };
2662     }
2663 root 1.1 }
2664     }
2665    
2666 root 1.59 sdl_init;
2667 root 1.1
2668 root 1.59 {
2669     my @fonts = map DC::find_rcfile "fonts/$_", qw(
2670     DejaVuSans.ttf
2671     DejaVuSansMono.ttf
2672     DejaVuSans-Bold.ttf
2673     DejaVuSansMono-Bold.ttf
2674     DejaVuSans-Oblique.ttf
2675     DejaVuSansMono-Oblique.ttf
2676     DejaVuSans-BoldOblique.ttf
2677     DejaVuSansMono-BoldOblique.ttf
2678     );
2679    
2680     DC::add_font $_ for @fonts;
2681    
2682     $FONT_PROP = new_from_file DC::Font $fonts[0];
2683     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2684 root 1.1
2685 root 1.59 $FONT_PROP->make_default;
2686 root 1.30
2687 root 1.59 DC::pango_init;
2688     }
2689 root 1.1
2690     # compare mono (ft) vs. rgba (cairo)
2691     # ft - 1.8s, cairo 3s, even in alpha-only mode
2692     # for my $rgba (0..1) {
2693     # my $t1 = Time::HiRes::time;
2694     # for (1..1000) {
2695 root 1.18 # my $layout = DC::Layout->new ($rgba);
2696 root 1.1 # $layout->set_text ("hallo" x 100);
2697     # $layout->render;
2698     # }
2699     # my $t2 = Time::HiRes::time;
2700     # warn $t2-$t1;
2701     # }
2702    
2703 root 1.59 video_init;
2704     audio_init;
2705     }
2706 root 1.1
2707 root 1.59 show_tip_of_the_day if $CFG->{show_tips};
2708 root 1.1
2709 root 1.59 our $STARTUP_CANCEL = EV::idle sub {
2710     undef $::STARTUP_CANCEL;
2711     $startup_done->();
2712     };
2713 root 1.1
2714 root 1.59 delete $SIG{__DIE__};
2715     EV::loop;
2716 root 1.1
2717     #video_shutdown;
2718     #audio_shutdown;
2719 root 1.59 DC::OpenGL::quit;
2720     DC::SDL_Quit;
2721     DC::DB::Server::stop;
2722     }
2723    
2724     DC::SDL_braino; # see sub above
2725 root 1.1
2726     =head1 NAME
2727    
2728     deliantra - A Deliantra MORPG game client
2729    
2730     =head1 SYNOPSIS
2731    
2732     Just run it - no commandline arguments are supported.
2733    
2734     =head1 USAGE
2735    
2736     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2737     be used in fullscreen mode and interactively.
2738    
2739     =head1 DEBUGGING
2740    
2741    
2742     CFPLUS_DEBUG - environment variable
2743    
2744     1 draw borders around widgets
2745     2 add low-level widget info to tooltips
2746     4 show fps
2747     8 suppress tooltips
2748    
2749     =head1 AUTHOR
2750    
2751 root 1.57 Marc Lehmann <deliantra@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2752 root 1.1
2753    
2754