ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.60
Committed: Tue Aug 19 21:48:28 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.59: +8 -1 lines
Log Message:
par the file, horrid

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