ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.3
Committed: Thu Dec 29 07:13:44 2011 UTC (12 years, 6 months ago) by root
Branch: MAIN
Changes since 1.2: +6 -3 lines
Log Message:
*** empty log message ***

File Contents

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