ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.75
Committed: Wed Sep 3 10:08:18 2008 UTC (15 years, 8 months ago) by root
Branch: MAIN
Changes since 1.74: +1 -1 lines
Log Message:
*** empty log message ***

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     [blue => "Blue"],
904     ],
905     tooltip => "Choose the User Interface theme that you like most :)",
906     on_changed => sub { my ($self, $value) = @_; $CFG->{uitheme} = $value; 0 }
907     );
908    
909 root 1.1 my $vidmode_tooltip =
910     "<b>Video Mode.</b> The video mode to use for fullscreen (and the window size for windowed operation). "
911     . "The format is <i>width</i> x <i>height</i> \@ <i>depth-per-channel</i> + <i>alpha-channel</i>.";
912    
913 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Video Mode");
914 root 1.18 $table->add_at (1, $row++, my $hbox = new DC::UI::HBox);
915 root 1.1
916 root 1.53 $hbox->add ($MODE_SLIDER = new DC::UI::Slider
917     force_w => $WIDTH * 0.1, expand => 1,
918 root 1.56 range => [ ($CFG->{sdl_mode}) x 3 ],
919 root 1.1 tooltip => $vidmode_tooltip);
920 root 1.18 $hbox->add (my $mode_label = new DC::UI::Label
921 root 1.22 height => 0.8, template => "9999x9999@9+9",
922 root 1.1 can_events => 1, tooltip => $vidmode_tooltip);
923    
924 root 1.53 $MODE_SLIDER->connect (changed => sub {
925 root 1.1 my ($self, $value) = @_;
926    
927     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
928     $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
929     });
930 root 1.53 $MODE_SLIDER->emit (changed => $MODE_SLIDER->{range}[0]);
931 root 1.1
932 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fullscreen");
933 root 1.18 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new DC::UI::CheckBox
934 root 1.1 state => $CFG->{fullscreen},
935     tooltip => "Bring the client into fullscreen mode.",
936     on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
937     );
938    
939 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Force OpenGL 1.1");
940 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
941 root 1.1 state => $CFG->{force_opengl11},
942 elmex 1.23 tooltip => "Limit Deliantra to use OpenGL 1.1 features only. This will normally result in "
943 root 1.1 . "higher memory usage and slower performance. It will, however, help tremendously on "
944     . "cards that claim to support a feature but fall back to software rendering. "
945     . "Nvidia Geforce FX cards are known to claim features the hardware doesn't support, "
946 root 1.52 . "but cards and drivers from other vendors (ATI) are often just as bad. "
947     . "<b>If you experience extremely low framerates and your card should do better, try this option.</b>",
948 root 1.1 on_changed => sub { my ($self, $value) = @_; $CFG->{force_opengl11} = $value; 0 }
949     );
950    
951 root 1.52 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Forbid Alpha");
952     $table->add_at (1, $row++, new DC::UI::CheckBox
953     state => $CFG->{disable_alpha},
954     tooltip => "Forbid off the use of the alpha channel. This makes Deliantra look a lot worse "
955     . "by disabling a number of textures and transparency effects. Normally, these "
956     . "effects do not cost a lot of resources, but some graphics cards might fall "
957     . "back to etxremely slow rendering if this is enabled. If disabling this option "
958     . "noticably improves the framerate of the client please report this! "
959     . "<b>If you experience extremely low framerates and your card should do better, try this option.</b>",
960     on_changed => sub {
961     my ($self, $value) = @_;
962     $CFG->{disable_alpha} = $value;
963     $SDL_REINIT = 1; # SDL_SetVideoMode ignores GL attr changes
964     0
965     }
966     );
967    
968 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Compress Textures");
969 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
970 root 1.1 state => $CFG->{texture_compression},
971     tooltip => "Use texture compression. Normally this will not reduce visual quality noticable but "
972 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). "
973     . "The compression algorithm can differ form card to card, so your mileage may vary. This setting is ignored in "
974     . "forced OpenGL 1.1 mode and when using the Apple renderer.",
975 root 1.1 on_changed => sub { my ($self, $value) = @_; $CFG->{texture_compression} = $value; 0 }
976     );
977    
978 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fast & Ugly");
979 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
980 root 1.1 state => $CFG->{fast},
981     tooltip => "Lower the visual quality considerably to speed up rendering.",
982     on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
983     );
984    
985 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "GUI Fontsize");
986 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
987 root 1.1 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
988     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
989     on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
990     );
991    
992 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
993 root 1.22 expand => 1, text => "Apply",
994 root 1.1 tooltip => "Apply the video settings above.",
995     on_activate => sub {
996     video_shutdown ();
997     video_init ();
998     0
999     }
1000     );
1001    
1002 root 1.56 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Smooth Movement");
1003     $table->add_at (1, $row++, new DC::UI::CheckBox
1004     state => $CFG->{smooth_movement},
1005     tooltip => "<b>Smooth Movement</b> tries to make movement, well, smoother, but also increases the framerate. "
1006     . "If you have a very slow system, non-accelerated drivers or plain dislike smooth scrolling, "
1007     . "then disable this option. Changes take effect immdiately.",
1008     on_changed => sub { my ($self, $value) = @_; $CFG->{smooth_movement} = $value; 0 }
1009     );
1010    
1011 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Scale");
1012 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1013 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
1014     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
1015     on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
1016     );
1017    
1018 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Smoothing");
1019 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1020 root 1.1 state => $CFG->{map_smoothing},
1021     tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
1022     . "This increases load on the graphics subsystem and works only with TRT servers. "
1023     . "Changes take effect at next login only.",
1024     on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
1025     );
1026    
1027 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fog of War");
1028 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1029 root 1.1 state => $CFG->{fow_enable},
1030     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
1031     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
1032     );
1033    
1034 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "FoW Intensity");
1035 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1036 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
1037     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
1038     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
1039     );
1040    
1041 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Fontsize");
1042 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1043 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
1044     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant, "
1045     . "but you still need to press apply to correctly re-layout the widget.",
1046 elmex 1.23 on_changed => sub { $MESSAGE_DIST->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
1047 root 1.1 );
1048    
1049 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge fontsize");
1050 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1051 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
1052     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
1053     on_changed => sub {
1054     $CFG->{gauge_fontsize} = $_[1];
1055     &set_gauge_window_fontsize;
1056     0
1057     }
1058     );
1059    
1060 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge size");
1061 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
1062 root 1.1 range => [$CFG->{gauge_size}, 0.2, 0.8],
1063     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
1064     on_changed => sub {
1065     $CFG->{gauge_size} = $_[1];
1066     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
1067     0
1068     }
1069     );
1070    
1071     $vbox
1072     }
1073    
1074     our $AUDIO_HW_CHUNKSIZE;
1075     our $AUDIO_INFO;
1076    
1077     sub audio_tab_update {
1078 root 1.18 my ($freq, $format, $chans) = DC::Mix_QuerySpec;
1079 root 1.1
1080     $AUDIO_HW_CHUNKSIZE->set_options ([
1081     [0, "default", "Use System Default"],
1082     map {
1083     my $ms = sprintf "%dms", 1000 * $_ / ($CFG->{audio_hw_frequency} || 22050);
1084     [$_, $ms, "$ms ($_ samples)"],
1085     } 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
1086     ]);
1087    
1088     my $text = !$freq
1089     ? "audio is off"
1090     : "audio is enabled\n"
1091     . "frequency (Hz): $freq\n"
1092     . "channels: $chans";
1093    
1094     $AUDIO_INFO->set_text ($text);
1095     }
1096    
1097     sub audio_setup {
1098 root 1.18 my $vbox = new DC::UI::VBox;
1099 root 1.1
1100 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 0, 1]);
1101 root 1.1
1102     my $row = 0;
1103    
1104 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Audio Enable");
1105 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1106 root 1.1 state => $CFG->{audio_enable},
1107     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.",
1108     on_changed => sub { $CFG->{audio_enable} = $_[1]; 1 }
1109     );
1110    
1111 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Sound Effects");
1112 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
1113 root 1.1 expand => 1, state => $CFG->{effects_enable},
1114     tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
1115     on_changed => sub {
1116     $CFG->{effects_enable} = $_[1];
1117     $CONN->update_fx_want if $CONN;
1118     1
1119     }
1120     );
1121 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
1122 root 1.1 expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
1123     tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
1124     . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
1125     on_changed => sub { $CFG->{effects_volume} = $_[1]; 1 }
1126     );
1127    
1128 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Background Music");
1129 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
1130 root 1.1 expand => 1, state => $CFG->{bgm_enable},
1131     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
1132     on_changed => sub {
1133     $CFG->{bgm_enable} = $_[1];
1134     $CONN->update_fx_want if $CONN;
1135     audio_music_push;
1136     1
1137     }
1138     );
1139 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
1140 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
1141     tooltip => "The volume of the background music. Changes are instant.",
1142     on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
1143     );
1144    
1145 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Frequency");
1146 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1147 root 1.1 c_colspan => 2, expand => 1,
1148     value => $CFG->{audio_hw_frequency},
1149     options => [
1150     [ 0, "default" , "Use System Default"],
1151     [11025, "11 kHz" , "11kHz (low quality)"],
1152     [22050, "22 kHz" , "22kHz (reduced quality)"],
1153     [44100, "44.1 kHz", "44.1kHz (cd quality)"],
1154     [48000, "48 kHz" , "48kHz (studio quality)"],
1155     ],
1156     tooltip => "The sampling frequency to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1157     on_changed => sub {
1158     $CFG->{audio_hw_frequency} = $_[1];
1159     audio_tab_update;
1160     1
1161     }
1162     );
1163    
1164 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Channels");
1165 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1166 root 1.1 c_colspan => 2, expand => 1,
1167     value => $CFG->{audio_hw_channels},
1168     options => [
1169     [0, "default" , "Use System Default"],
1170     [1, "Mono" , "Mono (single channel, low quality)"],
1171 root 1.6 [2, "Stereo" , "Stereo (dual channel, standard quality)"],
1172 root 1.1 [4, "4 Ch Surround", "4 Channel Surround Sound (3d sound, high quality)"],
1173     [6, "6 Ch Surround", "6 Channel Surround Sound (3d sound + center + lfe)"],
1174     ],
1175     tooltip => "The number of independent sound channels to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1176     on_changed => sub {
1177     $CFG->{audio_hw_channels} = $_[1];
1178     audio_tab_update;
1179     1
1180     }
1181     );
1182    
1183 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Latency");
1184 root 1.18 $table->add_at (1, $row++, $AUDIO_HW_CHUNKSIZE = new DC::UI::Selector
1185 root 1.1 c_colspan => 2, expand => 1,
1186     value => $CFG->{audio_hw_chunksize},
1187     tooltip => "The guarenteed latency. Lower is better, but also more cpu-intensive and might cause stuttering. If music playback "
1188     . "is stuttering, increase this value. Values of 50-100ms are optimal.",
1189     on_changed => sub {
1190     $CFG->{audio_hw_chunksize} = $_[1];
1191     audio_tab_update;
1192     1
1193     }
1194     );
1195    
1196     # should really be a slider
1197 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Mixer Voices");
1198 root 1.18 $table->add_at (1, $row++, new DC::UI::ValSlider
1199 root 1.1 c_colspan => 2, expand => 1,
1200     tooltip => "The number of simultaneous sound effects possible. Higher is better, but also more cpu-intensive and might cause stuttering.",
1201     range => [$::CFG->{audio_mix_channels}, 4, 32, 0, 1],
1202     template => ">= 99",
1203     on_changed => sub {
1204     my ($slider, $value) = @_;
1205    
1206     $CFG->{audio_mix_channels} = $value
1207     if $value;
1208     1;
1209     }
1210     );
1211    
1212 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
1213 root 1.22 c_colspan => 2, expand => 1, text => "Apply",
1214 root 1.1 tooltip => "Apply the audio settings",
1215     on_activate => sub {
1216     audio_shutdown ();
1217     audio_init ();
1218     0
1219     }
1220     );
1221    
1222 root 1.18 $vbox->add (new DC::UI::FancyFrame
1223 root 1.1 expand => 1,
1224     label => "Audio Info",
1225 root 1.18 child => ($AUDIO_INFO = new DC::UI::Label ellipsise => 0),
1226 root 1.1 );
1227    
1228     audio_tab_update;
1229    
1230     $vbox
1231     }
1232    
1233     sub set_gauge_window_fontsize {
1234     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1235     $_->set_fontsize ($::CFG->{gauge_fontsize});
1236     }
1237     }
1238    
1239     sub make_gauge_window {
1240     my $gh = int $HEIGHT * $CFG->{gauge_size};
1241    
1242 root 1.18 my $win = new DC::UI::Frame (
1243 root 1.1 force_x => 0,
1244     force_y => "max",
1245     force_w => $WIDTH,
1246     force_h => $gh,
1247     );
1248    
1249 root 1.18 $win->add (my $hbox = new DC::UI::HBox
1250 root 1.1 children => [
1251 root 1.18 (new DC::UI::HBox expand => 1),
1252     (new DC::UI::VBox children => [
1253     (new DC::UI::Empty expand => 1),
1254     (new DC::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new DC::UI::Table)),
1255 root 1.1 ]),
1256 root 1.18 (my $vbox = new DC::UI::VBox),
1257 root 1.1 ],
1258     );
1259    
1260 root 1.18 $vbox->add (new DC::UI::HBox
1261 root 1.1 expand => 1,
1262     children => [
1263 root 1.18 (new DC::UI::Empty expand => 1),
1264     (my $hb = new DC::UI::HBox),
1265 root 1.1 ],
1266     );
1267    
1268 root 1.18 $hb->add (my $hg = new DC::UI::Gauge type => 'hp', tooltip => "#stat_health");
1269     $hb->add (my $mg = new DC::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1270     $hb->add (my $gg = new DC::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1271     $hb->add (my $fg = new DC::UI::Gauge type => 'food', tooltip => "#stat_food");
1272    
1273 root 1.22 $vbox->add (my $exp = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1274 root 1.18 $vbox->add (my $prg = new DC::UI::ExperienceProgress);
1275     $vbox->add (my $sklprg = new DC::UI::ExperienceProgress);
1276 root 1.22 $vbox->add (my $rng = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1277 root 1.1
1278     $GAUGES = {
1279     exp => $exp, prg => $prg, sklprg => $sklprg,
1280     win => $win, range => $rng,
1281     hp => $hg, mana => $mg, grace => $gg, food => $fg,
1282     };
1283    
1284     &set_gauge_window_fontsize;
1285    
1286     $win
1287     }
1288    
1289     sub debug_setup {
1290 root 1.18 my $table = new DC::UI::Table;
1291 root 1.1
1292 root 1.18 $table->add_at (0, 0, new DC::UI::Label text => "Widget Borders");
1293     $table->add_at (1, 0, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1294     $table->add_at (0, 1, new DC::UI::Label text => "Tooltip Widget Info");
1295     $table->add_at (1, 1, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1296     $table->add_at (0, 2, new DC::UI::Label text => "Show FPS");
1297     $table->add_at (1, 2, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1298     $table->add_at (0, 3, new DC::UI::Label text => "Suppress Tooltips");
1299     $table->add_at (1, 3, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1300     $table->add_at (0, 4, new DC::UI::Button text => "die on click(tm)", on_activate => sub { &DC::debug() } );
1301    
1302 root 1.21 $table->add_at (0, 5, new DC::UI::TextEdit text => "line1\0152\0153\nµikachu\nづx゙つ゛");#d#
1303 root 1.18
1304     $table->add_at (7,7, my $t = new DC::UI::Table expand => 0);
1305 root 1.20 $t->add_at (0,0, new DC::UI::Label text => "a a", c_rowspan => 1, c_colspan => 2);
1306     $t->add_at (2,0, new DC::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1307     $t->add_at (1,2, new DC::UI::Label text => "c c", c_rowspan => 1, c_colspan => 2);
1308     $t->add_at (0,1, new DC::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1309 root 1.18 $t->add_at (1,1, new DC::UI::Label text => "e");
1310 root 1.1
1311 root 1.18 $table->add_at (7, 6, my $c = new DC::UI::Canvas);
1312 root 1.1
1313     $c->add_items ({
1314     type => "line_loop",
1315     color => [0, 1, 0],
1316     width => 9,
1317     coord_mode => "abs",
1318     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1319     });
1320    
1321     $c->add_items ({
1322     type => "lines",
1323     color => [1, 1, 0],
1324     width => 2,
1325     coord_mode => "rel",
1326     coord => [[0,0], [1,1], [1,0], [0,1]],
1327     });
1328    
1329     $c->add_items ({
1330     type => "polygon",
1331     color => [0, 0.43, 0],
1332     width => 2,
1333     coord_mode => "rel",
1334     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1335     });
1336    
1337     $table
1338     }
1339    
1340     sub stats_window {
1341 root 1.18 my $r = new DC::UI::ScrolledWindow (
1342 root 1.1 expand => 1,
1343     scroll_y => 1
1344     );
1345 root 1.18 $r->add (my $vb = new DC::UI::VBox);
1346 root 1.1
1347 root 1.18 $vb->add (new DC::UI::FancyFrame
1348 root 1.1 label => "Player",
1349 root 1.18 child => (my $pi = new DC::UI::VBox),
1350 root 1.1 );
1351    
1352 root 1.22 $pi->add ($STATWIDS->{title} = new DC::UI::Label text => "Title:", expand => 1, align => 0,
1353 root 1.1 can_hover => 1, can_events => 1,
1354     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1355 root 1.22 $pi->add ($STATWIDS->{map} = new DC::UI::Label align => 0, text => "Map:", expand => 1,
1356 root 1.1 can_hover => 1, can_events => 1,
1357     tooltip => "The map you are currently on (if supported by the server).");
1358    
1359 root 1.18 $pi->add (my $hb0 = new DC::UI::HBox);
1360 root 1.22 $hb0->add ($STATWIDS->{weight} = new DC::UI::Label text => "Weight:", expand => 1, align => 0,
1361 root 1.1 can_hover => 1, can_events => 1,
1362     tooltip => "The weight of the player including all inventory items.");
1363 root 1.22 $hb0->add ($STATWIDS->{m_weight} = new DC::UI::Label align => 0, text => "Max weight:", expand => 1,
1364 root 1.1 can_hover => 1, can_events => 1,
1365     tooltip => "The weight limit: you cannot carry more than this.");
1366    
1367 root 1.18 $vb->add (new DC::UI::FancyFrame
1368 root 1.1 label => "Primary/Secondary Statistics",
1369 root 1.18 child => (my $hb = new DC::UI::HBox expand => 1),
1370 root 1.1 );
1371 root 1.18 $hb->add (my $tbl = new DC::UI::Table expand => 1);
1372 root 1.1
1373     my $color2 = [1, 1, 0];
1374    
1375     for (
1376     [0, 0, st_str => "Str", 30],
1377     [0, 1, st_dex => "Dex", 30],
1378     [0, 2, st_con => "Con", 30],
1379     [0, 3, st_int => "Int", 30],
1380     [0, 4, st_wis => "Wis", 30],
1381     [0, 5, st_pow => "Pow", 30],
1382     [0, 6, st_cha => "Cha", 30],
1383    
1384     [2, 0, st_wc => "Wc", -120],
1385     [2, 1, st_ac => "Ac", -120],
1386     [2, 2, st_dam => "Dam", 120],
1387     [2, 3, st_arm => "Arm", 120],
1388     [2, 4, st_spd => "Spd", 10.54],
1389     [2, 5, st_wspd => "WSp", 10.54],
1390     ) {
1391     my ($col, $row, $id, $label, $template) = @$_;
1392    
1393 root 1.18 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new DC::UI::Label
1394 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1,
1395     align => 1, template => $template, tooltip => "#stat_$label");
1396 root 1.18 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new DC::UI::Label
1397 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2,
1398     align => 0, text => $label, tooltip => "#stat_$label");
1399 root 1.1 }
1400    
1401 root 1.18 $vb->add (new DC::UI::FancyFrame
1402 root 1.1 label => "Resistancies",
1403 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]),
1404 root 1.1 );
1405    
1406     my $row = 0;
1407     my $col = 0;
1408    
1409     my %resist_names = (
1410     slow => ["Slow",
1411     "<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.)"],
1412     holyw => ["Holy Word",
1413     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1414     conf => ["Confusion",
1415     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1416     fire => ["Fire",
1417     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1418     depl => ["Depletion",
1419     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1420     magic => ["Magic",
1421     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1422     drain => ["Draining",
1423     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1424     acid => ["Acid",
1425     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1426     pois => ["Poison",
1427     "<b>Poison</b> (resistance to getting poisoned)"],
1428     para => ["Paralysation",
1429     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1430     deat => ["Death",
1431     "<b>Death</b> (resistance against death spells)"],
1432     phys => ["Physical",
1433     "<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.)"],
1434     blind => ["Blind",
1435     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1436     fear => ["Fear",
1437     "<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)"],
1438     tund => ["Turn undead",
1439     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1440     elec => ["Electricity",
1441     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1442     cold => ["Cold",
1443     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1444     ghit => ["Ghost hit",
1445     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1446     );
1447    
1448     for (qw/slow holyw conf fire depl magic
1449     drain acid pois para deat phys
1450     blind fear tund elec cold ghit/)
1451     {
1452 root 1.22 $tbl2->add_at ($col + 2, $row,
1453 root 1.1 $STATWIDS->{"res_$_"} =
1454 root 1.18 new DC::UI::Label
1455 root 1.1 font => $FONT_FIXED,
1456     template => "-100%",
1457 root 1.22 align => 1,
1458 root 1.1 can_events => 1,
1459     can_hover => 1,
1460     tooltip => $resist_names{$_}->[1],
1461     );
1462 root 1.18 $tbl2->add_at ($col + 1, $row, new DC::UI::Image
1463 root 1.1 font => $FONT_FIXED,
1464     can_hover => 1,
1465     can_events => 1,
1466     path => "ui/resist/resist_$_.png",
1467     tooltip => $resist_names{$_}->[1],
1468     );
1469 root 1.22 $tbl2->add_at ($col + 0, $row, new DC::UI::Label
1470 root 1.1 text => $resist_names{$_}->[0],
1471     font => $FONT_FIXED,
1472 root 1.22 align => 1,
1473 root 1.1 can_hover => 1,
1474     can_events => 1,
1475     tooltip => $resist_names{$_}->[1],
1476     );
1477    
1478     $row++;
1479     if ($row % 6 == 0) {
1480 root 1.22 $col += 4;
1481 root 1.1 $row = 0;
1482     }
1483     }
1484    
1485     #update_stats_window ({});
1486    
1487     $r
1488     }
1489    
1490     sub skill_window {
1491 root 1.18 my $sw = new DC::UI::ScrolledWindow (expand => 1);
1492 root 1.1
1493 root 1.18 $sw->add ($STATWIDS->{skill_tbl} = new DC::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1494 root 1.1
1495     $sw
1496     }
1497    
1498     sub formsep($) {
1499     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1500     }
1501    
1502     my $METASERVER_ATIME;
1503    
1504     sub update_metaserver {
1505     my ($metaserver_dialog) = @_;
1506    
1507     $METASERVER = $metaserver_dialog
1508     if defined $metaserver_dialog;
1509    
1510     return if $METASERVER_ATIME > time;
1511     $METASERVER_ATIME = time + 60;
1512    
1513     my $table = $METASERVER->{table};
1514     $table->clear;
1515 root 1.18 $table->add_at (0, 0, my $label = new DC::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1516 root 1.1
1517     my $ok = 0;
1518    
1519 root 1.18 DC::background {
1520     my $ua = DC::lwp_useragent;
1521 root 1.1
1522 root 1.18 DC::background_msg DC::decode_json +(DC::lwp_check $ua->get ($META_SERVER))->decoded_content;
1523 root 1.1 } sub {
1524     my ($msg) = @_;
1525     if ($msg) {
1526     $table->clear;
1527    
1528     my @tip = (
1529     "The current number of users logged in on the server.",
1530     "The hostname of the server.",
1531     "The time this server has been running without being restarted.",
1532     "Short information about this server provided by its admins.",
1533     );
1534     my @col = qw(#Users Host Uptime Version Description);
1535 root 1.18 $table->add_at ($_, 0, new DC::UI::Label
1536 root 1.22 can_hover => 1, can_events => 1, fg => [1, 1, 0],
1537 root 1.1 text => $col[$_], tooltip => $tip[$_])
1538     for 0 .. $#col;
1539    
1540 root 1.22 my @align = qw(1 0.5 1 1 0);
1541 root 1.1
1542     my $y = 0;
1543     for my $m (@{ $msg->{servers} }) {
1544     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1545     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1546    
1547     for ($desc) {
1548     s/<br>/\n/gi;
1549     s/<li>/\n· /gi;
1550     s/<.*?>//sgi;
1551     s/&amp;/&/g;
1552     s/&lt;/</g;
1553     s/&gt;/>/g;
1554     }
1555    
1556     $uptime = sprintf "%dd %02d:%02d:%02d",
1557     (int $uptime / 86400),
1558     (int $uptime / 3600) % 24,
1559     (int $uptime / 60) % 60,
1560     $uptime % 60;
1561    
1562     $m = [$users, $host, $uptime, $version, $desc];
1563    
1564     $y++;
1565    
1566 root 1.18 $table->add_at (scalar @$m, $y, new DC::UI::VBox children => [
1567     (new DC::UI::Button
1568 root 1.1 text => "Use",
1569     tooltip => "Put this server into the <b>Host:Port</b> field",
1570     on_activate => sub {
1571     $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1572     $METASERVER->hide;
1573     0
1574     },
1575     ),
1576 root 1.18 (new DC::UI::Empty expand => 1),
1577 root 1.1 ]);
1578    
1579 root 1.18 $table->add_at ($_, $y, new DC::UI::Label
1580 root 1.1 max_w => $::WIDTH * 0.4,
1581     ellipsise => 0,
1582     align => $align[$_],
1583     text => $m->[$_],
1584     tooltip => $tip[$_],
1585     fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1586     can_hover => 1,
1587     can_events => 1,
1588     fontsize => 0.8)
1589     for 0 .. $#$m;
1590     }
1591     } else {
1592     $ok or $label->set_text ("error while contacting metaserver");
1593     }
1594     };
1595    
1596     }
1597    
1598     sub metaserver_dialog {
1599 root 1.18 my $vbox = new DC::UI::VBox;
1600     my $table = new DC::UI::Table;
1601     $vbox->add (new DC::UI::ScrolledWindow expand => 1, child => $table);
1602 root 1.1
1603 root 1.18 my $dialog = new DC::UI::Toplevel
1604 root 1.1 title => "Server List",
1605     name => 'metaserver_dialog',
1606     x => 'center',
1607     y => 'center',
1608     z => 3,
1609     force_w => $::WIDTH * 0.9,
1610     force_h => $::HEIGHT * 0.7,
1611     child => $vbox,
1612     has_close_button => 1,
1613     table => $table,
1614     on_visibility_change => sub {
1615     update_metaserver ($_[0]) if $_[1];
1616     0
1617     },
1618     ;
1619    
1620     $dialog
1621     }
1622    
1623     sub login_setup {
1624 root 1.18 my $vbox = new DC::UI::VBox;
1625 root 1.1
1626 root 1.18 $vbox->add (new DC::UI::FancyFrame
1627 root 1.1 label => "Login Settings",
1628 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1629 root 1.1 );
1630    
1631 root 1.22 $table->add_at (0, 4, new DC::UI::Label align => 1, text => "Username");
1632 root 1.18 $table->add_at (1, 4, new DC::UI::Entry
1633 root 1.1 text => $CFG->{profile}{default}{user},
1634     tooltip => "The name of your character on the server.",
1635     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1636     );
1637    
1638 root 1.22 $table->add_at (0, 5, new DC::UI::Label align => 1, text => "Password");
1639 root 1.18 $table->add_at (1, 5, new DC::UI::Entry
1640 root 1.1 text => $CFG->{profile}{default}{password},
1641     hidden => 1,
1642     tooltip => "The password for your character.",
1643     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1644     );
1645    
1646 root 1.18 $table->add_at (1, 11, $LOGIN_BUTTON = new DC::UI::Button
1647 root 1.1 expand => 1,
1648     text => "Login / Register",
1649     tooltip => "This button will either login to the account configured above or register a new account.",
1650     on_activate => sub {
1651     $CONN ? stop_game
1652     : start_game;
1653     1
1654     },
1655     );
1656    
1657 root 1.18 $vbox->add (new DC::UI::FancyFrame
1658 root 1.61 label => "How to Play",
1659 root 1.65 min_h => 240,
1660 root 1.22 child => (new DC::UI::Label valign => 0, ellipsise => 0,
1661 root 1.1 markup =>
1662 root 1.63 "First select a suitable video resolution in the <b>Graphics</b> tab, above.\n\n"
1663 root 1.62 . "Then register a new account (or use an existing one if you have one). "
1664     . "To register an account, choose a username that hasn't been taken yet (just guess) and "
1665 root 1.1 . "try to log-in. Follow the instructions in the Log tab in the message window.",
1666     ),
1667     );
1668    
1669     $vbox
1670     }
1671    
1672     sub server_setup {
1673 root 1.18 my $vbox = new DC::UI::VBox;
1674 root 1.1
1675 root 1.18 $vbox->add (new DC::UI::FancyFrame
1676 root 1.1 label => "Connection Settings",
1677 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1678 root 1.1 );
1679    
1680     my $row = 0;
1681    
1682 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Host:Port");
1683 root 1.1 {
1684 root 1.18 $table->add_at (1, $row, my $vbox = new DC::UI::VBox);
1685 root 1.1
1686     $vbox->add (
1687 root 1.18 $HOST_ENTRY = new DC::UI::Entry
1688 root 1.1 expand => 1,
1689     text => $CFG->{profile}{default}{host},
1690 root 1.28 tooltip => "The hostname or ip address of the Deliantra server to connect to (e.g. <b>gameserver.deliantra.net</b>)",
1691 root 1.1 on_changed => sub {
1692     my ($self, $value) = @_;
1693     $CFG->{profile}{default}{host} = $value;
1694     1
1695     }
1696     );
1697    
1698 root 1.5 if (0) { #d# disabled
1699 root 1.18 $vbox->add (new DC::UI::Button
1700 root 1.1 expand => 1,
1701     text => "Server List",
1702     other => $METASERVER,
1703 root 1.28 tooltip => "Show a list of available Deliantra servers",
1704 root 1.1 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1705     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1706     );
1707 root 1.5 }#d#
1708 root 1.1 }
1709    
1710 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Map Size");
1711 root 1.18 $table->add_at (1, $row, new DC::UI::Slider
1712 root 1.1 force_w => 100,
1713     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1714     tooltip => "This is the size of the portion of the map update the server sends you. "
1715     . "If you set this to a high value you will be able to see further, "
1716     . "but you also increase bandwidth requirements and latency. "
1717     . "This option is only used once at log-in.",
1718     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1719     );
1720    
1721 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Output-Rate");
1722 root 1.18 $table->add_at (1, $row, new DC::UI::Entry
1723 root 1.1 text => $CFG->{output_rate},
1724     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1725     . "when sending data. When 0 or unset, the server "
1726     . "default will be used, which is usually around 100kb/s. Most servers will "
1727     . "dynamically find an optimal rate, so adjust this only when necessary.",
1728     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1729     );
1730    
1731 root 1.18 $vbox->add (new DC::UI::FancyFrame
1732 root 1.1 label => "Server Info",
1733 root 1.18 child => ($SERVER_INFO = new DC::UI::Label ellipsise => 0),
1734 root 1.1 );
1735    
1736     $vbox
1737     }
1738    
1739     sub client_setup {
1740 root 1.18 my $table = new DC::UI::Table expand => 1, col_expand => [0, 1];
1741 root 1.1
1742     my $row = 0;
1743    
1744 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Tip of the day");
1745 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1746 root 1.1 state => $CFG->{show_tips},
1747     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1748     on_changed => sub {
1749     my ($self, $value) = @_;
1750     $CFG->{show_tips} = $value;
1751     0
1752     }
1753     );
1754    
1755 root 1.37 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Window Size");
1756 root 1.18 $table->add_at (1, $row++, my $saycmd = new DC::UI::Entry
1757 root 1.1 text => $CFG->{logview_max_par},
1758 root 1.37 tooltip => "This is maximum number of messages remembered in the <b>Message</b> window. If the server "
1759 root 1.1 . "sends more messages than this number, older messages get removed to save memory and "
1760     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1761     on_changed => sub {
1762     my ($self, $value) = @_;
1763 root 1.28 $MESSAGE_DIST->set_max_par ($CFG->{logview_max_par} = $value*1);
1764 root 1.1 0
1765     },
1766     );
1767    
1768     $table
1769     }
1770    
1771     sub autopickup_setup {
1772 root 1.18 my $r = new DC::UI::ScrolledWindow (
1773 root 1.1 expand => 1,
1774     scroll_y => 1
1775     );
1776 root 1.18 $r->add (my $table = new DC::UI::Table
1777 root 1.1 row_expand => [0],
1778     col_expand => [0, 1, 0, 1],
1779     );
1780    
1781     for (
1782     ["General", 0, 0,
1783     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1784     ["Inhibit autopickup" => PICKUP_INHIBIT],
1785     ["Stop before pickup" => PICKUP_STOP],
1786     ["Debug autopickup" => PICKUP_DEBUG],
1787     ],
1788     ["Weapons", 0, 6,
1789     ["All weapons" => PICKUP_ALLWEAPON],
1790     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1791     ["Bows" => PICKUP_BOW],
1792     ["Arrows" => PICKUP_ARROW],
1793     ],
1794     ["Armour", 0, 12,
1795     ["Helmets" => PICKUP_HELMET],
1796     ["Shields" => PICKUP_SHIELD],
1797     ["Body Armour" => PICKUP_ARMOUR],
1798     ["Boots" => PICKUP_BOOTS],
1799     ["Gloves" => PICKUP_GLOVES],
1800     ["Cloaks" => PICKUP_CLOAK],
1801     ],
1802    
1803     ["Readables", 2, 0,
1804     ["Spellbooks" => PICKUP_SPELLBOOK],
1805     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1806     ["Normal Books/Scrolls" => PICKUP_READABLES],
1807     ],
1808     ["Misc", 2, 5,
1809     ["Food" => PICKUP_FOOD],
1810     ["Drinks" => PICKUP_DRINK],
1811     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1812     ["Keys" => PICKUP_KEY],
1813     ["Magical Items" => PICKUP_MAGICAL],
1814     ["Potions" => PICKUP_POTION],
1815     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1816     ["Ignore cursed" => PICKUP_NOT_CURSED],
1817     ["Jewelery" => PICKUP_JEWELS],
1818     ["Flesh" => PICKUP_FLESH],
1819     ],
1820 root 1.58 ["Value/Weight ratio", 2, 17]
1821 root 1.1 )
1822     {
1823     my ($title, $x, $y, @bits) = @$_;
1824 root 1.18 $table->add_at ($x, $y, new DC::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1825 root 1.1
1826     for (@bits) {
1827     ++$y;
1828    
1829     my $mask = $_->[1];
1830 root 1.18 $table->add_at ($x , $y, new DC::UI::Label text => $_->[0], align => 1, expand => 1);
1831     $table->add_at ($x+1, $y, my $checkbox = new DC::UI::CheckBox
1832 root 1.1 state => $::CFG->{pickup} & $mask,
1833     on_changed => sub {
1834     my ($box, $value) = @_;
1835    
1836     if ($value) {
1837     $::CFG->{pickup} |= $mask;
1838     } else {
1839     $::CFG->{pickup} &= ~$mask;
1840     }
1841    
1842     $::CONN->send_command ("pickup $::CFG->{pickup}")
1843     if defined $::CONN;
1844    
1845     0
1846     });
1847    
1848     ${$_->[2]} = $checkbox if $_->[2];
1849     }
1850     }
1851    
1852 root 1.18 $table->add_at (2, 18, new DC::UI::ValSlider
1853 root 1.1 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1854     template => ">= 99",
1855 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).",
1856 root 1.1 to_value => sub { ">= " . 5 * $_[0] },
1857     on_changed => sub {
1858     my ($slider, $value) = @_;
1859    
1860     $::CFG->{pickup} &= ~0xF;
1861     $::CFG->{pickup} |= int $value
1862     if $value;
1863     1;
1864     });
1865    
1866 root 1.18 $table->add_at (3, 18, new DC::UI::Button
1867 root 1.1 text => "set",
1868     on_activate => sub {
1869     $::CONN->send_command ("pickup $::CFG->{pickup}")
1870     if defined $::CONN;
1871     0
1872     });
1873    
1874     $r
1875     }
1876    
1877     my %SORT_ORDER = (
1878 elmex 1.41 type => sub {
1879     sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @_
1880     },
1881 root 1.1 mtime => sub {
1882     my $NOW = time;
1883     sort {
1884     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1885     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1886    
1887     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1888     or $btime <=> $atime
1889     or $a->{type} <=> $b->{type}
1890     } @_
1891     },
1892     weight => sub { sort {
1893     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1894     or $a->{type} <=> $b->{type}
1895     } @_ },
1896     );
1897    
1898     sub inventory_widget {
1899 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1900 root 1.1
1901 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1902 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1903 root 1.1
1904 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1905 root 1.1
1906     use sort 'stable';
1907    
1908 root 1.18 $hb1->add (new DC::UI::Selector
1909 root 1.1 value => $::CFG->{inv_sort},
1910     options => [
1911     [type => "Type/Name"],
1912     [mtime => "Recent/Normal/Locked"],
1913     [weight => "Weight/Type"],
1914     ],
1915     on_changed => sub {
1916     $::CFG->{inv_sort} = $_[1];
1917     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1918     },
1919     );
1920 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1921 root 1.58 #TODO# update to weight/maxweight
1922 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1923 root 1.1
1924 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1925     $sw1->add ($INV = new DC::UI::Inventory);
1926 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1927    
1928 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1929 root 1.1
1930 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1931 root 1.1
1932 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1933     $sw2->add ($INVR = new DC::UI::Inventory);
1934 root 1.1
1935     # XXX: Call after $INVR = ... because set_opencont sets the items
1936 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
1937 root 1.1
1938     $hb
1939     }
1940    
1941     sub media_window {
1942 root 1.18 my $vb = new DC::UI::VBox;
1943 root 1.1
1944 root 1.18 $vb->add (new DC::UI::FancyFrame
1945 root 1.1 label => "Currently playing music",
1946 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1947     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
1948 root 1.1 );
1949    
1950 root 1.18 $vb->add (new DC::UI::FancyFrame
1951 root 1.1 label => "Other media used in this session",
1952     expand => 1,
1953 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
1954 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1955     );
1956    
1957     $vb
1958     }
1959    
1960     sub add_license {
1961     my ($meta) = @_;
1962    
1963     $meta = $meta->{data}
1964     or return;
1965    
1966     $meta->{license} || $meta->{author} || $meta->{source}
1967     or return;
1968    
1969     $LICENSE_WIDGET->add_paragraph ({
1970     fg => [1, 1, 1, 1],
1971     markup => "<small>"
1972 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
1973     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
1974     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
1975     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
1976 root 1.1 . "</small>",
1977     });
1978     $LICENSE_WIDGET->scroll_to_bottom;
1979     }
1980    
1981     sub toggle_player_page {
1982     my ($widget) = @_;
1983    
1984     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1985     $PL_WINDOW->hide;
1986     } else {
1987     $PL_NOTEBOOK->set_current_page ($widget);
1988     $PL_WINDOW->show;
1989     }
1990     }
1991    
1992     sub player_window {
1993 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
1994 root 1.1 x => "center",
1995     y => "center",
1996     force_w => $WIDTH * 9/10,
1997     force_h => $HEIGHT * 9/10,
1998     title => "Player",
1999     name => "playerbook",
2000     has_close_button => 1
2001     ;
2002    
2003     my $ntb =
2004     $PL_NOTEBOOK =
2005 root 1.18 new DC::UI::Notebook expand => 1;
2006 root 1.1
2007     $ntb->add_tab (
2008     "Statistics (F2)" => $STATS_PAGE = stats_window,
2009     "Shows statistics, where all your Stats and Resistances are shown."
2010     );
2011     $ntb->add_tab (
2012     "Skills (F3)" => $SKILL_PAGE = skill_window,
2013     "Shows all your Skills."
2014     );
2015    
2016 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
2017     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
2018 root 1.1 $ntb->add_tab (
2019     "Spellbook (F4)" => $spellsw,
2020     "Displays all spells you have and lets you edit keyboard shortcuts for them."
2021     );
2022     $ntb->add_tab (
2023     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
2024     "Toggles the inventory window, where you can manage your loot (or treasures :). "
2025     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
2026     );
2027     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
2028     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
2029    
2030     $ntb->add_tab (Media => media_window,
2031     "License, Author and Source info for media sent by the server.");
2032    
2033     $ntb->set_current_page ($INVENTORY_PAGE);
2034    
2035     $plwin->add ($ntb);
2036     $plwin
2037     }
2038    
2039     sub keyboard_setup {
2040 root 1.18 DC::Macro::keyboard_setup
2041 root 1.1 }
2042    
2043     sub help_window {
2044 root 1.18 my $win = new DC::UI::Toplevel
2045 root 1.1 x => 'center',
2046     y => 'center',
2047     z => 4,
2048     name => 'doc_browser',
2049     force_w => int $WIDTH * 7/8,
2050     force_h => int $HEIGHT * 7/8,
2051     title => "Help Browser",
2052     has_close_button => 1;
2053    
2054 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
2055 root 1.1
2056 root 1.18 $vbox->add (new DC::UI::FancyFrame
2057 root 1.1 label => "Navigation",
2058 root 1.18 child => (my $buttons = new DC::UI::HBox),
2059 root 1.1 );
2060 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2061 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2062    
2063     my @history;
2064     my @future;
2065     my $curnode;
2066    
2067     my $load_node; $load_node = sub {
2068     my ($node, $para) = @_;
2069    
2070     $buttons->clear;
2071    
2072 root 1.18 $buttons->add (new DC::UI::Button
2073 root 1.1 text => "⇤",
2074     tooltip => "back to the starting page",
2075     on_activate => sub {
2076     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2077     unshift @future, @history;
2078     @history = ();
2079     $load_node->(@{shift @future});
2080     },
2081     );
2082    
2083     if (@history) {
2084 root 1.18 $buttons->add (new DC::UI::Button
2085 root 1.1 text => "⋘",
2086 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
2087 root 1.1 on_activate => sub {
2088     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2089     $load_node->(@{pop @history});
2090     },
2091     );
2092     }
2093    
2094     if (@future) {
2095 root 1.18 $buttons->add (new DC::UI::Button
2096 root 1.1 text => "⋙",
2097 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
2098 root 1.1 on_activate => sub {
2099     push @history, [$curnode, $viewer->current_paragraph];
2100     $load_node->(@{shift @future});
2101     },
2102     );
2103     }
2104    
2105 root 1.18 $buttons->add (new DC::UI::Label text => " ");
2106 root 1.1
2107 root 1.18 my @path = DC::Pod::full_path_of $node;
2108 root 1.1 pop @path; # drop current node
2109    
2110     for my $node (@path) {
2111 root 1.18 $buttons->add (new DC::UI::Button
2112 root 1.31 text => $node->[DC::Pod::N_KW][0],
2113 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
2114 root 1.1 on_activate => sub {
2115     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2116     $load_node->($node);
2117     },
2118     );
2119 root 1.18 $buttons->add (new DC::UI::Label text => "/");
2120 root 1.1 }
2121    
2122 root 1.31 $buttons->add (new DC::UI::Label text => $node->[DC::Pod::N_KW][0], padding_x => 4, padding_y => 4);
2123 root 1.1
2124     $curnode = $node;
2125    
2126     $viewer->clear;
2127 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
2128 root 1.1 $viewer->scroll_to ($para);
2129     };
2130    
2131 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
2132 root 1.1
2133 root 1.18 $DC::Pod::goto_document = sub {
2134 root 1.1 my (@path) = @_;
2135    
2136     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2137    
2138 root 1.18 $load_node->((DC::Pod::find @path)[0]);
2139 root 1.1 $win->show;
2140     };
2141    
2142     $win
2143     }
2144    
2145     sub open_string_query {
2146     my ($title, $cb, $txt, $tooltip) = @_;
2147 root 1.18 my $dialog = new DC::UI::Toplevel
2148 root 1.1 x => "center",
2149     y => "center",
2150     z => 50,
2151     force_w => $WIDTH * 4/5,
2152     title => $title;
2153    
2154     $dialog->add (
2155 root 1.18 my $e = new DC::UI::Entry
2156 root 1.1 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2157     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2158     tooltip => $tooltip
2159     );
2160    
2161     $e->grab_focus;
2162     $e->set_text ($txt) if $txt;
2163     $dialog->show;
2164     }
2165    
2166     sub open_quit_dialog {
2167     unless ($QUIT_DIALOG) {
2168 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2169 root 1.1 x => "center",
2170     y => "center",
2171     z => 50,
2172     title => "Really Quit?",
2173     on_key_down => sub {
2174     my ($dialog, $ev) = @_;
2175     $ev->{sym} == 27 and $dialog->hide;
2176     }
2177     ;
2178    
2179 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2180 root 1.1
2181 root 1.18 $vb->add (new DC::UI::Label
2182 root 1.1 text => "You should find a savebed and apply it first!",
2183     max_w => $WIDTH * 0.25,
2184     ellipsize => 0,
2185     );
2186 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2187     $hb->add (new DC::UI::Button
2188 root 1.1 text => "Ok",
2189     expand => 1,
2190     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2191     );
2192 root 1.18 $hb->add (new DC::UI::Button
2193 root 1.1 text => "Quit anyway",
2194     expand => 1,
2195 root 1.66 on_activate => sub {
2196     crash "Quit anyway";
2197     EV::unloop EV::UNLOOP_ALL;
2198     },
2199 root 1.1 );
2200     }
2201    
2202     $QUIT_DIALOG->show;
2203     $QUIT_DIALOG->grab_focus;
2204     }
2205    
2206     sub show_tip_of_the_day {
2207     # find all tips
2208 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2209 root 1.1
2210 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2211 root 1.1 my ($todindex) = @_;
2212     $todindex = 0 if $todindex >= @tod;
2213 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2214 root 1.1
2215     # create dialog
2216     my $dialog;
2217    
2218     my $close = sub {
2219     $dialog->destroy;
2220     };
2221    
2222 root 1.18 $dialog = new DC::UI::Toplevel
2223 root 1.1 x => "center",
2224     y => "center",
2225     z => 3,
2226     name => 'tip_of_the_day',
2227     force_w => int $WIDTH * 4/9,
2228     force_h => int $WIDTH * 2/9,
2229     title => "Tip of the day #" . (1 + $todindex),
2230 root 1.18 child => my $vbox = new DC::UI::VBox,
2231 root 1.1 has_close_button => 1,
2232     on_delete => $close,
2233     ;
2234    
2235 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2236 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2237 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2238 root 1.1
2239 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2240 root 1.1
2241 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2242 root 1.1 text => "Close",
2243     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>.",
2244     on_activate => $close,
2245     );
2246    
2247 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2248 root 1.1 text => "Next",
2249     tooltip => "Show the next <b>Tip of the day</b>.",
2250     on_activate => sub {
2251     $close->();
2252     &show_tip_of_the_day;
2253     },
2254     );
2255    
2256     $dialog->show;
2257     };
2258     }
2259    
2260     sub sdl_init {
2261 root 1.52 DC::SDL_Init DC::SDL_INIT_AUDIO #| DC::SDL_NOPARACHUTE
2262 root 1.1 and die "SDL::Init failed!\n";
2263     }
2264    
2265     sub video_init {
2266 root 1.74 DC::set_theme $CFG->{uitheme};
2267 root 1.73
2268 root 1.52 DC::SDL_InitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2269     $SDL_REINIT = 0;
2270    
2271     @SDL_MODES = DC::SDL_ListModes 8, $CFG->{disable_alpha} ? 0 : 8;
2272     @SDL_MODES = DC::SDL_ListModes 8, 8 unless @SDL_MODES;
2273     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2274     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2275    
2276     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2277    
2278 root 1.61 if (!defined $CFG->{sdl_mode} or $CFG->{sdl_mode} > $#SDL_MODES) {
2279     $CFG->{sdl_mode} = 0; # lowest resolution by default
2280    
2281 root 1.72 # now choose biggest mode <= 1024x768
2282 root 1.61 for (0 .. $#SDL_MODES) {
2283     if ($SDL_MODES[$_][0] * $SDL_MODES[$_][1] <= 1024 * 768) {
2284     $CFG->{sdl_mode} = $_;
2285     }
2286 root 1.52 }
2287     }
2288 root 1.1
2289     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2290    
2291     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2292     $FULLSCREEN = $CFG->{fullscreen};
2293     $FAST = $CFG->{fast};
2294    
2295 root 1.59 # due to mac os x braindamage, we simply retry with !fullscreen in case of an error
2296 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2297 root 1.59 or DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, !$FULLSCREEN
2298 root 1.18 or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2299 root 1.1
2300     $SDL_ACTIVE = 1;
2301     $LAST_REFRESH = time - 0.01;
2302    
2303 root 1.18 DC::OpenGL::init;
2304     DC::Macro::init;
2305 root 1.1
2306     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2307    
2308 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2309 root 1.1
2310     #############################################################################
2311    
2312     if ($DEBUG_STATUS) {
2313 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2314 root 1.1 } else {
2315     # create/configure the widgets
2316    
2317 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2318 root 1.1 my (undef, $ev) = @_;
2319    
2320 root 1.18 if (my @macros = DC::Macro::find $ev) {
2321     DC::Macro::execute $_ for @macros;
2322 root 1.1
2323     return 1;
2324     }
2325    
2326     0
2327     });
2328    
2329 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2330 root 1.1 padding => 0,
2331     z => 100,
2332     force_x => "max",
2333     force_y => 0;
2334     $DEBUG_STATUS->show;
2335    
2336 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2337 root 1.29
2338     $MODBOX = new DC::UI::Label
2339     can_events => 1,
2340     can_hover => 1,
2341     markup => "",
2342     align => 0,
2343     font => $FONT_FIXED,
2344 root 1.30 tooltip => "#modifier_box",
2345     tooltip_width => 0.67,
2346     ;
2347 root 1.29
2348     update_modbox;
2349 root 1.1
2350 root 1.18 (new DC::UI::Frame
2351 root 1.1 bg => [0, 0, 0, 0.4],
2352     force_x => 0,
2353     force_y => "max",
2354 root 1.29 child => (my $LR = new DC::UI::VBox),
2355 root 1.1 )->show;
2356    
2357 root 1.29 $LR->add ($STATUSBOX);
2358     $LR->add ($MODBOX);
2359     $LR->add (new DC::UI::Label
2360     align => 0,
2361     markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode",
2362     fontsize => 0.5,
2363     fg => [1, 1, 0, 0.7],
2364     );
2365    
2366 root 1.18 DC::UI::Toplevel->new (
2367 root 1.2 title => "Minimap",
2368 root 1.1 name => "mapmap",
2369     x => 0,
2370     y => $FONTSIZE + 8,
2371     border_bg => [1, 1, 1, 192/255],
2372     bg => [1, 1, 1, 0],
2373 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2374 root 1.75 tooltip => "<b>Minimap</b>. This will display an overview of the surrounding areas.",
2375 root 1.1 ),
2376     )->show;
2377    
2378 root 1.18 $MAPWIDGET = new DC::MapWidget;
2379 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2380     my ($mapwidget, $preset) = @_;
2381    
2382 elmex 1.23 $MESSAGE_DIST->activate_console ($preset)
2383     if $MESSAGE_DIST;
2384 root 1.1 });
2385     $MAPWIDGET->show;
2386     $MAPWIDGET->grab_focus;
2387    
2388 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2389 root 1.1 command => { },
2390     tooltip => "#completer_help",
2391     ;
2392    
2393 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2394 root 1.1 title => "Setup",
2395     name => "setup_dialog",
2396     x => 'center',
2397     y => 'center',
2398     z => 2,
2399     force_w => $::WIDTH * 0.6,
2400     force_h => $::HEIGHT * 0.6,
2401     has_close_button => 1,
2402     ;
2403    
2404     $METASERVER = metaserver_dialog;
2405 root 1.39 # the name is changed to not conflict with the older name as users could have hidden it
2406 root 1.40 $MESSAGE_WINDOW = new DC::UI::Dockbar
2407     name => "message_window2",
2408     title => 'Messages',
2409     force_w => $::WIDTH * 0.6,
2410     force_h => $::HEIGHT * 0.25,
2411     ;
2412    
2413 elmex 1.23 $MESSAGE_DIST = new DC::MessageDistributor dockbar => $MESSAGE_WINDOW;
2414 root 1.1
2415 root 1.38 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1,
2416 root 1.18 filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2417 root 1.1
2418     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2419     "Configure the server to play on, your username and password.");
2420     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2421     "Configure other server related options.");
2422     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2423     "Configure various client-specific settings.");
2424     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2425     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2426     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2427     "Configure the use of audio, sound effects and background music.");
2428     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2429     "Lets you define, edit and delete key bindings."
2430     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2431     . "with nothing set and the recording started. After doing the actions you "
2432     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2433     . "After pressing the combo the binding will be saved automatically and the "
2434     . "binding editor closes");
2435     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2436     "Some debuggin' options. Do not ask.");
2437    
2438 root 1.18 $BUTTONBAR = new DC::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2439 root 1.1
2440 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2441 root 1.1 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2442    
2443 root 1.37 # $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2444     # tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2445 root 1.1
2446     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
2447    
2448 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Playerbook", other => player_window,
2449 root 1.1 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2450    
2451 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2452 root 1.1 text => "Save Config",
2453     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2454     on_activate => sub {
2455 root 1.18 $::CFG->{layout} = DC::UI::get_layout;
2456 root 1.35 DC::write_cfg;
2457 root 1.1 status "Configuration Saved";
2458     0
2459     },
2460     );
2461    
2462 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2463 root 1.1 tooltip => "View Documentation");
2464    
2465 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2466 root 1.1 text => "Quit",
2467     tooltip => "Terminates the program",
2468     on_activate => sub {
2469     if ($CONN) {
2470     open_quit_dialog;
2471     } else {
2472 root 1.4 EV::unloop EV::UNLOOP_ALL;
2473 root 1.1 }
2474     0
2475     },
2476     );
2477    
2478     $BUTTONBAR->show;
2479     $SETUP_DIALOG->show;
2480     $MESSAGE_WINDOW->show;
2481     }
2482    
2483 root 1.72 $MODE_SLIDER->set_range ([$CFG->{sdl_mode}, 0, scalar @SDL_MODES, 1, 1]);
2484 root 1.53 $MODE_SLIDER->emit (changed => $CFG->{sdl_mode});
2485    
2486     $CAVEAT_LABEL->set_text ("None :)");
2487 root 1.55 $CAVEAT_LABEL->set_text ("Software Rendering (very slow)")
2488 root 1.53 unless DC::SDL_GL_GetAttribute DC::SDL_GL_ACCELERATED_VISUAL;
2489    
2490 root 1.1 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2491     }
2492    
2493     sub video_shutdown {
2494 root 1.18 DC::OpenGL::shutdown;
2495 root 1.52 DC::SDL_QuitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2496 root 1.1
2497     undef $SDL_ACTIVE;
2498     }
2499    
2500     my %animate_object;
2501     my $animate_timer;
2502    
2503     my $fps = 9;
2504    
2505     sub force_refresh {
2506     if ($ENV{CFPLUS_DEBUG} & 4) {
2507     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2508     debug sprintf "%3.2f", $fps;
2509     }
2510    
2511 root 1.16 undef $WANT_REFRESH;
2512     $_[0]->stop;
2513 root 1.12
2514 root 1.18 $DC::UI::ROOT->draw;
2515     DC::SDL_GL_SwapBuffers;
2516 root 1.1 $LAST_REFRESH = $NOW;
2517     }
2518    
2519 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2520 root 1.1
2521 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2522     $NOW = EV::now;
2523 root 1.1
2524     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2525 root 1.18 for DC::poll_events;
2526 root 1.1
2527     if (%animate_object) {
2528     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2529 root 1.16 $WANT_REFRESH = 1;
2530 root 1.1 }
2531 root 1.16
2532     $want_refresh->start
2533     if $WANT_REFRESH;
2534 root 1.4 };
2535 root 1.1
2536     sub animation_start {
2537     my ($widget) = @_;
2538     $animate_object{$widget} = $widget;
2539     }
2540    
2541     sub animation_stop {
2542     my ($widget) = @_;
2543     delete $animate_object{$widget};
2544     }
2545    
2546     %SDL_CB = (
2547 root 1.18 DC::SDL_QUIT => sub {
2548 root 1.66 crash "SDL_QUIT";
2549 root 1.4 EV::unloop EV::UNLOOP_ALL;
2550 root 1.1 },
2551 root 1.18 DC::SDL_VIDEORESIZE => sub {
2552 root 1.1 },
2553 root 1.18 DC::SDL_VIDEOEXPOSE => sub {
2554     DC::UI::full_refresh;
2555 root 1.1 },
2556 root 1.18 DC::SDL_ACTIVEEVENT => sub {
2557 root 1.12 # not useful, as APPACTIVE includes only iconified state, not unmapped
2558 root 1.18 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2559     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2560     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2561     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2562     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2563 root 1.1 },
2564 root 1.18 DC::SDL_KEYDOWN => sub {
2565     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2566 root 1.1 # alt-enter
2567 root 1.52 video_shutdown;
2568 root 1.1 $FULLSCREEN_ENABLE->toggle;
2569     video_init;
2570     } else {
2571 root 1.29 &DC::UI::feed_sdl_key_down_event;
2572 root 1.1 }
2573 root 1.29 update_modbox;
2574     },
2575     DC::SDL_KEYUP => sub {
2576     &DC::UI::feed_sdl_key_up_event;
2577     update_modbox;
2578 root 1.1 },
2579 root 1.18 DC::SDL_MOUSEMOTION => \&DC::UI::feed_sdl_motion_event,
2580     DC::SDL_MOUSEBUTTONDOWN => \&DC::UI::feed_sdl_button_down_event,
2581     DC::SDL_MOUSEBUTTONUP => \&DC::UI::feed_sdl_button_up_event,
2582     DC::SDL_USEREVENT => sub {
2583 root 1.1 if ($_[0]{code} == 1) {
2584     audio_channel_finished $_[0]{data1};
2585     } elsif ($_[0]{code} == 0) {
2586     audio_music_finished;
2587     }
2588     },
2589     );
2590    
2591     #############################################################################
2592    
2593 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2594     EV::unloop;
2595     #d# TODO calling exit here hangs the process in some futex
2596     };
2597 root 1.1
2598 root 1.59 # due to mac os x + sdl combined briandamage, we need this contortion
2599     sub main {
2600     {
2601     DC::Pod::load_docwiki DC::find_rcfile "docwiki.pst";
2602 root 1.34
2603 root 1.59 if (-e "$Deliantra::VARDIR/client.cf") {
2604     DC::read_cfg "$Deliantra::VARDIR/client.cf";
2605     } else {
2606     #TODO: compatibility cruft
2607     DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2608     print STDERR "INFO: used old configuration file\n";
2609     }
2610 root 1.15
2611 root 1.59 DC::DB::Server::run;
2612 root 1.35
2613 root 1.59 if ($CFG->{db_schema} < 1) {
2614     warn "INFO: upgrading database schema from 0 to 1, mapcache and tilecache will be lost\n";
2615     DC::DB::nuke_db;
2616     $CFG->{db_schema} = 1;
2617     DC::write_cfg;
2618     }
2619 root 1.35
2620 root 1.59 DC::DB::open_db;
2621 root 1.1
2622 root 1.59 DC::UI::set_layout ($::CFG->{layout});
2623 root 1.1
2624 root 1.59 my %DEF_CFG = (
2625 root 1.61 sdl_mode => undef,
2626 root 1.59 fullscreen => 1,
2627     fast => 0,
2628     force_opengl11 => undef,
2629     disable_alpha => 0,
2630     smooth_movement => 1,
2631     texture_compression => 1,
2632     map_scale => 1,
2633     fow_enable => 1,
2634     fow_intensity => 0,
2635     map_smoothing => 1,
2636     gui_fontsize => 1,
2637     log_fontsize => 0.7,
2638     gauge_fontsize => 1,
2639     gauge_size => 0.35,
2640     stat_fontsize => 0.7,
2641     mapsize => 100,
2642     audio_enable => 1,
2643     audio_hw_channels => 0,
2644     audio_hw_frequency => 0,
2645     audio_hw_chunksize => 0,
2646     audio_mix_channels => 8,
2647     effects_enable => 1,
2648     effects_volume => 1,
2649     bgm_enable => 1,
2650     bgm_volume => 0.5,
2651     output_rate => "",
2652     pickup => 0,
2653     inv_sort => "mtime",
2654     default => "profile", # default profile
2655     show_tips => 1,
2656     logview_max_par => 1000,
2657     shift_fire_stop => 0,
2658 root 1.74 uitheme => "wood",
2659 root 1.59 );
2660    
2661     while (my ($k, $v) = each %DEF_CFG) {
2662     $CFG->{$k} = $v unless exists $CFG->{$k};
2663     }
2664 root 1.1
2665 root 1.59 $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2666     $PROFILE = $CFG->{profile}{default};
2667 root 1.1
2668 root 1.59 # convert old bindings (only default profile matters)
2669     if (my $bindings = delete $PROFILE->{bindings}) {
2670     while (my ($mod, $syms) = each %$bindings) {
2671     while (my ($sym, $cmds) = each %$syms) {
2672     push @{ $PROFILE->{macro} }, {
2673     accelkey => [$mod*1, $sym*1],
2674     action => $cmds,
2675     };
2676     }
2677 root 1.1 }
2678     }
2679    
2680 root 1.59 sdl_init;
2681 root 1.1
2682 root 1.59 {
2683     my @fonts = map DC::find_rcfile "fonts/$_", qw(
2684     DejaVuSans.ttf
2685     DejaVuSansMono.ttf
2686     DejaVuSans-Bold.ttf
2687     DejaVuSansMono-Bold.ttf
2688     DejaVuSans-Oblique.ttf
2689     DejaVuSansMono-Oblique.ttf
2690     DejaVuSans-BoldOblique.ttf
2691     DejaVuSansMono-BoldOblique.ttf
2692     );
2693    
2694     DC::add_font $_ for @fonts;
2695    
2696     $FONT_PROP = new_from_file DC::Font $fonts[0];
2697     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2698 root 1.1
2699 root 1.59 $FONT_PROP->make_default;
2700 root 1.30
2701 root 1.59 DC::pango_init;
2702     }
2703 root 1.1
2704     # compare mono (ft) vs. rgba (cairo)
2705     # ft - 1.8s, cairo 3s, even in alpha-only mode
2706     # for my $rgba (0..1) {
2707     # my $t1 = Time::HiRes::time;
2708     # for (1..1000) {
2709 root 1.18 # my $layout = DC::Layout->new ($rgba);
2710 root 1.1 # $layout->set_text ("hallo" x 100);
2711     # $layout->render;
2712     # }
2713     # my $t2 = Time::HiRes::time;
2714     # warn $t2-$t1;
2715     # }
2716    
2717 root 1.59 video_init;
2718     audio_init;
2719     }
2720 root 1.1
2721 root 1.59 show_tip_of_the_day if $CFG->{show_tips};
2722 root 1.1
2723 root 1.59 our $STARTUP_CANCEL = EV::idle sub {
2724     undef $::STARTUP_CANCEL;
2725     $startup_done->();
2726     };
2727 root 1.1
2728 root 1.59 delete $SIG{__DIE__};
2729     EV::loop;
2730 root 1.1
2731     #video_shutdown;
2732     #audio_shutdown;
2733 root 1.59 DC::OpenGL::quit;
2734     DC::SDL_Quit;
2735     DC::DB::Server::stop;
2736     }
2737    
2738     DC::SDL_braino; # see sub above
2739 root 1.1
2740     =head1 NAME
2741    
2742     deliantra - A Deliantra MORPG game client
2743    
2744     =head1 SYNOPSIS
2745    
2746     Just run it - no commandline arguments are supported.
2747    
2748     =head1 USAGE
2749    
2750     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2751     be used in fullscreen mode and interactively.
2752    
2753     =head1 DEBUGGING
2754    
2755    
2756     CFPLUS_DEBUG - environment variable
2757    
2758     1 draw borders around widgets
2759     2 add low-level widget info to tooltips
2760     4 show fps
2761     8 suppress tooltips
2762    
2763     =head1 AUTHOR
2764    
2765 root 1.57 Marc Lehmann <deliantra@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2766 root 1.1
2767    
2768