ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.76
Committed: Wed Sep 3 12:50:43 2008 UTC (15 years, 8 months ago) by root
Branch: MAIN
CVS Tags: rel-0_9976
Changes since 1.75: +2 -1 lines
Log Message:
metal

File Contents

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