ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.1
Committed: Tue Dec 27 07:23:33 2011 UTC (12 years, 6 months ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

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