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