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