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