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