ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.61
Committed: Fri Aug 22 18:20:17 2008 UTC (15 years, 9 months ago) by root
Branch: MAIN
Changes since 1.60: +13 -7 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.7 if ($ENV{DELIANTRA_CORO_DEBUG}) {
4     eval '
5     use Coro;
6     use Coro::EV;
7     use Coro::Debug;
8     our $debug = new_unix_server Coro::Debug "/tmp/dc";
9     ';
10     }
11    
12 root 1.1 # do splash-screen thingy on win32
13     my $startup_done = sub { };
14     BEGIN {
15     if (%PAR::LibCache && $^O eq "MSWin32") {
16     while (my ($filename, $zip) = each %PAR::LibCache) {
17     $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp");
18     }
19    
20     require Win32::GUI::SplashScreen;
21    
22     Win32::GUI::SplashScreen::Show (
23     -file => "$ENV{PAR_TEMP}/SPLASH.bmp",
24     );
25    
26     $startup_done = sub {
27     Win32::GUI::SplashScreen::Done (1);
28     };
29     }
30     }
31    
32     use strict;
33     use utf8;
34    
35     use Carp 'verbose';
36    
37     # do things only needed for single-binary version (par)
38     BEGIN {
39     if (%PAR::LibCache) {
40     @INC = grep ref, @INC; # weed out all paths except pars loader refs
41    
42     my $root = $ENV{PAR_TEMP};
43    
44     while (my ($filename, $zip) = each %PAR::LibCache) {
45     for ($zip->memberNames) {
46     next unless /^root\/(.*)/;
47     $zip->extractMember ($_, "$root/$1")
48     unless -e "$root/$1";
49     }
50     }
51    
52     if ($^O eq "MSWin32") {
53     # pango is relocatable on win32
54 root 1.60 } elsif (-e "$root/pangoversion") {
55 root 1.1 open my $fh, "<:perlio", "$root/pangoversion"
56     or die "pangoversion: $!";
57     my $PANGO = <$fh>;
58     # unix, need to patch pango rc file
59     open my $fh, "<:perlio", "$root/usr/lib/pango/$PANGO/module-files.d/libpango1.0-0.modules"
60     or die "$root/usr/lib/$PANGO/module-files.d/libpango1.0-0.modules: $!";
61     local $/;
62     my $rc = <$fh>;
63     $rc =~ s/^\//$root\//gm; # replace abs paths by relative ones
64    
65     mkdir "$root/pango-modules";
66     open my $fh, ">:perlio", "$root/pango-modules/pango.modules"
67     or die "$root/pango-modules/pango.modules: $!";
68     print $fh $rc;
69    
70     $ENV{PANGO_RC_FILE} = "$root/pango.rc";
71     open my $fh, ">:perlio", $ENV{PANGO_RC_FILE}
72     or die "$ENV{PANGO_RC_FILE}: $!";
73     print $fh "[Pango]\nModuleFiles = $root/pango-modules\n";
74 root 1.60 } else {
75     # OS X
76     $ENV{FC_CONFIG_FILE} = "$root/fonts.conf"; # no effect??!?!
77     $ENV{FC_CONFIG_DIR} = $root; # no effect??!?!
78     $ENV{PANGO_RC_FILE} = "$root/pango.rc";
79     $ENV{DYLD_LIBRARY_PATH} = $root;
80     chdir $root; # for pango modules, maybe other things
81 root 1.1 }
82    
83     unshift @INC, $root;
84     }
85     }
86    
87 root 1.17 # prepend private library directory
88     BEGIN {
89     for (grep !ref, @INC) {
90 root 1.24 my $path = "$_/Deliantra/Client/private";
91 root 1.17 if (-d $path) {
92     unshift @INC, $path;
93     last;
94     }
95     }
96     }
97    
98 root 1.1 # need to do it again because that pile of garbage called PAR nukes it before main
99     unshift @INC, $ENV{PAR_TEMP}
100     if %PAR::LibCache;
101    
102     use Time::HiRes 'time';
103 root 1.4 use EV;
104 root 1.1 use List::Util qw(max min);
105    
106 root 1.14 use Deliantra;
107     use Deliantra::Protocol::Constants;
108 root 1.1
109 root 1.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.61 label => "How to Play",
1602 root 1.1 min_h => 200,
1603 root 1.22 child => (new DC::UI::Label valign => 0, ellipsise => 0,
1604 root 1.1 markup =>
1605 root 1.61 "First select a suitable video resolution in the Graphics tab, above.\n\n"
1606     . "Then register a new account (or use an existibg one if you have one). "
1607     . "To register an account, choose a username that hasn't been taken yet and "
1608 root 1.1 . "try to log-in. Follow the instructions in the Log tab in the message window.",
1609     ),
1610     );
1611    
1612     $vbox
1613     }
1614    
1615     sub server_setup {
1616 root 1.18 my $vbox = new DC::UI::VBox;
1617 root 1.1
1618 root 1.18 $vbox->add (new DC::UI::FancyFrame
1619 root 1.1 label => "Connection Settings",
1620 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1621 root 1.1 );
1622    
1623     my $row = 0;
1624    
1625 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Host:Port");
1626 root 1.1 {
1627 root 1.18 $table->add_at (1, $row, my $vbox = new DC::UI::VBox);
1628 root 1.1
1629     $vbox->add (
1630 root 1.18 $HOST_ENTRY = new DC::UI::Entry
1631 root 1.1 expand => 1,
1632     text => $CFG->{profile}{default}{host},
1633 root 1.28 tooltip => "The hostname or ip address of the Deliantra server to connect to (e.g. <b>gameserver.deliantra.net</b>)",
1634 root 1.1 on_changed => sub {
1635     my ($self, $value) = @_;
1636     $CFG->{profile}{default}{host} = $value;
1637     1
1638     }
1639     );
1640    
1641 root 1.5 if (0) { #d# disabled
1642 root 1.18 $vbox->add (new DC::UI::Button
1643 root 1.1 expand => 1,
1644     text => "Server List",
1645     other => $METASERVER,
1646 root 1.28 tooltip => "Show a list of available Deliantra servers",
1647 root 1.1 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1648     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1649     );
1650 root 1.5 }#d#
1651 root 1.1 }
1652    
1653 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Map Size");
1654 root 1.18 $table->add_at (1, $row, new DC::UI::Slider
1655 root 1.1 force_w => 100,
1656     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1657     tooltip => "This is the size of the portion of the map update the server sends you. "
1658     . "If you set this to a high value you will be able to see further, "
1659     . "but you also increase bandwidth requirements and latency. "
1660     . "This option is only used once at log-in.",
1661     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1662     );
1663    
1664 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Output-Rate");
1665 root 1.18 $table->add_at (1, $row, new DC::UI::Entry
1666 root 1.1 text => $CFG->{output_rate},
1667     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1668     . "when sending data. When 0 or unset, the server "
1669     . "default will be used, which is usually around 100kb/s. Most servers will "
1670     . "dynamically find an optimal rate, so adjust this only when necessary.",
1671     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1672     );
1673    
1674 root 1.18 $vbox->add (new DC::UI::FancyFrame
1675 root 1.1 label => "Server Info",
1676 root 1.18 child => ($SERVER_INFO = new DC::UI::Label ellipsise => 0),
1677 root 1.1 );
1678    
1679     $vbox
1680     }
1681    
1682     sub client_setup {
1683 root 1.18 my $table = new DC::UI::Table expand => 1, col_expand => [0, 1];
1684 root 1.1
1685     my $row = 0;
1686    
1687 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Tip of the day");
1688 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1689 root 1.1 state => $CFG->{show_tips},
1690     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1691     on_changed => sub {
1692     my ($self, $value) = @_;
1693     $CFG->{show_tips} = $value;
1694     0
1695     }
1696     );
1697    
1698 root 1.37 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Window Size");
1699 root 1.18 $table->add_at (1, $row++, my $saycmd = new DC::UI::Entry
1700 root 1.1 text => $CFG->{logview_max_par},
1701 root 1.37 tooltip => "This is maximum number of messages remembered in the <b>Message</b> window. If the server "
1702 root 1.1 . "sends more messages than this number, older messages get removed to save memory and "
1703     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1704     on_changed => sub {
1705     my ($self, $value) = @_;
1706 root 1.28 $MESSAGE_DIST->set_max_par ($CFG->{logview_max_par} = $value*1);
1707 root 1.1 0
1708     },
1709     );
1710    
1711     $table
1712     }
1713    
1714     sub autopickup_setup {
1715 root 1.18 my $r = new DC::UI::ScrolledWindow (
1716 root 1.1 expand => 1,
1717     scroll_y => 1
1718     );
1719 root 1.18 $r->add (my $table = new DC::UI::Table
1720 root 1.1 row_expand => [0],
1721     col_expand => [0, 1, 0, 1],
1722     );
1723    
1724     for (
1725     ["General", 0, 0,
1726     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1727     ["Inhibit autopickup" => PICKUP_INHIBIT],
1728     ["Stop before pickup" => PICKUP_STOP],
1729     ["Debug autopickup" => PICKUP_DEBUG],
1730     ],
1731     ["Weapons", 0, 6,
1732     ["All weapons" => PICKUP_ALLWEAPON],
1733     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1734     ["Bows" => PICKUP_BOW],
1735     ["Arrows" => PICKUP_ARROW],
1736     ],
1737     ["Armour", 0, 12,
1738     ["Helmets" => PICKUP_HELMET],
1739     ["Shields" => PICKUP_SHIELD],
1740     ["Body Armour" => PICKUP_ARMOUR],
1741     ["Boots" => PICKUP_BOOTS],
1742     ["Gloves" => PICKUP_GLOVES],
1743     ["Cloaks" => PICKUP_CLOAK],
1744     ],
1745    
1746     ["Readables", 2, 0,
1747     ["Spellbooks" => PICKUP_SPELLBOOK],
1748     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1749     ["Normal Books/Scrolls" => PICKUP_READABLES],
1750     ],
1751     ["Misc", 2, 5,
1752     ["Food" => PICKUP_FOOD],
1753     ["Drinks" => PICKUP_DRINK],
1754     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1755     ["Keys" => PICKUP_KEY],
1756     ["Magical Items" => PICKUP_MAGICAL],
1757     ["Potions" => PICKUP_POTION],
1758     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1759     ["Ignore cursed" => PICKUP_NOT_CURSED],
1760     ["Jewelery" => PICKUP_JEWELS],
1761     ["Flesh" => PICKUP_FLESH],
1762     ],
1763 root 1.58 ["Value/Weight ratio", 2, 17]
1764 root 1.1 )
1765     {
1766     my ($title, $x, $y, @bits) = @$_;
1767 root 1.18 $table->add_at ($x, $y, new DC::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1768 root 1.1
1769     for (@bits) {
1770     ++$y;
1771    
1772     my $mask = $_->[1];
1773 root 1.18 $table->add_at ($x , $y, new DC::UI::Label text => $_->[0], align => 1, expand => 1);
1774     $table->add_at ($x+1, $y, my $checkbox = new DC::UI::CheckBox
1775 root 1.1 state => $::CFG->{pickup} & $mask,
1776     on_changed => sub {
1777     my ($box, $value) = @_;
1778    
1779     if ($value) {
1780     $::CFG->{pickup} |= $mask;
1781     } else {
1782     $::CFG->{pickup} &= ~$mask;
1783     }
1784    
1785     $::CONN->send_command ("pickup $::CFG->{pickup}")
1786     if defined $::CONN;
1787    
1788     0
1789     });
1790    
1791     ${$_->[2]} = $checkbox if $_->[2];
1792     }
1793     }
1794    
1795 root 1.18 $table->add_at (2, 18, new DC::UI::ValSlider
1796 root 1.1 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1797     template => ">= 99",
1798 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).",
1799 root 1.1 to_value => sub { ">= " . 5 * $_[0] },
1800     on_changed => sub {
1801     my ($slider, $value) = @_;
1802    
1803     $::CFG->{pickup} &= ~0xF;
1804     $::CFG->{pickup} |= int $value
1805     if $value;
1806     1;
1807     });
1808    
1809 root 1.18 $table->add_at (3, 18, new DC::UI::Button
1810 root 1.1 text => "set",
1811     on_activate => sub {
1812     $::CONN->send_command ("pickup $::CFG->{pickup}")
1813     if defined $::CONN;
1814     0
1815     });
1816    
1817     $r
1818     }
1819    
1820     my %SORT_ORDER = (
1821 elmex 1.41 type => sub {
1822     sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @_
1823     },
1824 root 1.1 mtime => sub {
1825     my $NOW = time;
1826     sort {
1827     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1828     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1829    
1830     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1831     or $btime <=> $atime
1832     or $a->{type} <=> $b->{type}
1833     } @_
1834     },
1835     weight => sub { sort {
1836     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1837     or $a->{type} <=> $b->{type}
1838     } @_ },
1839     );
1840    
1841     sub inventory_widget {
1842 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1843 root 1.1
1844 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1845 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1846 root 1.1
1847 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1848 root 1.1
1849     use sort 'stable';
1850    
1851 root 1.18 $hb1->add (new DC::UI::Selector
1852 root 1.1 value => $::CFG->{inv_sort},
1853     options => [
1854     [type => "Type/Name"],
1855     [mtime => "Recent/Normal/Locked"],
1856     [weight => "Weight/Type"],
1857     ],
1858     on_changed => sub {
1859     $::CFG->{inv_sort} = $_[1];
1860     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1861     },
1862     );
1863 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1864 root 1.58 #TODO# update to weight/maxweight
1865 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1866 root 1.1
1867 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1868     $sw1->add ($INV = new DC::UI::Inventory);
1869 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1870    
1871 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1872 root 1.1
1873 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1874 root 1.1
1875 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1876     $sw2->add ($INVR = new DC::UI::Inventory);
1877 root 1.1
1878     # XXX: Call after $INVR = ... because set_opencont sets the items
1879 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
1880 root 1.1
1881     $hb
1882     }
1883    
1884     sub media_window {
1885 root 1.18 my $vb = new DC::UI::VBox;
1886 root 1.1
1887 root 1.18 $vb->add (new DC::UI::FancyFrame
1888 root 1.1 label => "Currently playing music",
1889 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1890     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
1891 root 1.1 );
1892    
1893 root 1.18 $vb->add (new DC::UI::FancyFrame
1894 root 1.1 label => "Other media used in this session",
1895     expand => 1,
1896 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
1897 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1898     );
1899    
1900     $vb
1901     }
1902    
1903     sub add_license {
1904     my ($meta) = @_;
1905    
1906     $meta = $meta->{data}
1907     or return;
1908    
1909     $meta->{license} || $meta->{author} || $meta->{source}
1910     or return;
1911    
1912     $LICENSE_WIDGET->add_paragraph ({
1913     fg => [1, 1, 1, 1],
1914     markup => "<small>"
1915 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
1916     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
1917     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
1918     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
1919 root 1.1 . "</small>",
1920     });
1921     $LICENSE_WIDGET->scroll_to_bottom;
1922     }
1923    
1924     sub toggle_player_page {
1925     my ($widget) = @_;
1926    
1927     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1928     $PL_WINDOW->hide;
1929     } else {
1930     $PL_NOTEBOOK->set_current_page ($widget);
1931     $PL_WINDOW->show;
1932     }
1933     }
1934    
1935     sub player_window {
1936 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
1937 root 1.1 x => "center",
1938     y => "center",
1939     force_w => $WIDTH * 9/10,
1940     force_h => $HEIGHT * 9/10,
1941     title => "Player",
1942     name => "playerbook",
1943     has_close_button => 1
1944     ;
1945    
1946     my $ntb =
1947     $PL_NOTEBOOK =
1948 root 1.18 new DC::UI::Notebook expand => 1;
1949 root 1.1
1950     $ntb->add_tab (
1951     "Statistics (F2)" => $STATS_PAGE = stats_window,
1952     "Shows statistics, where all your Stats and Resistances are shown."
1953     );
1954     $ntb->add_tab (
1955     "Skills (F3)" => $SKILL_PAGE = skill_window,
1956     "Shows all your Skills."
1957     );
1958    
1959 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1960     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
1961 root 1.1 $ntb->add_tab (
1962     "Spellbook (F4)" => $spellsw,
1963     "Displays all spells you have and lets you edit keyboard shortcuts for them."
1964     );
1965     $ntb->add_tab (
1966     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1967     "Toggles the inventory window, where you can manage your loot (or treasures :). "
1968     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1969     );
1970     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
1971     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1972    
1973     $ntb->add_tab (Media => media_window,
1974     "License, Author and Source info for media sent by the server.");
1975    
1976     $ntb->set_current_page ($INVENTORY_PAGE);
1977    
1978     $plwin->add ($ntb);
1979     $plwin
1980     }
1981    
1982     sub keyboard_setup {
1983 root 1.18 DC::Macro::keyboard_setup
1984 root 1.1 }
1985    
1986     sub help_window {
1987 root 1.18 my $win = new DC::UI::Toplevel
1988 root 1.1 x => 'center',
1989     y => 'center',
1990     z => 4,
1991     name => 'doc_browser',
1992     force_w => int $WIDTH * 7/8,
1993     force_h => int $HEIGHT * 7/8,
1994     title => "Help Browser",
1995     has_close_button => 1;
1996    
1997 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
1998 root 1.1
1999 root 1.18 $vbox->add (new DC::UI::FancyFrame
2000 root 1.1 label => "Navigation",
2001 root 1.18 child => (my $buttons = new DC::UI::HBox),
2002 root 1.1 );
2003 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2004 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2005    
2006     my @history;
2007     my @future;
2008     my $curnode;
2009    
2010     my $load_node; $load_node = sub {
2011     my ($node, $para) = @_;
2012    
2013     $buttons->clear;
2014    
2015 root 1.18 $buttons->add (new DC::UI::Button
2016 root 1.1 text => "⇤",
2017     tooltip => "back to the starting page",
2018     on_activate => sub {
2019     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2020     unshift @future, @history;
2021     @history = ();
2022     $load_node->(@{shift @future});
2023     },
2024     );
2025    
2026     if (@history) {
2027 root 1.18 $buttons->add (new DC::UI::Button
2028 root 1.1 text => "⋘",
2029 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
2030 root 1.1 on_activate => sub {
2031     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2032     $load_node->(@{pop @history});
2033     },
2034     );
2035     }
2036    
2037     if (@future) {
2038 root 1.18 $buttons->add (new DC::UI::Button
2039 root 1.1 text => "⋙",
2040 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
2041 root 1.1 on_activate => sub {
2042     push @history, [$curnode, $viewer->current_paragraph];
2043     $load_node->(@{shift @future});
2044     },
2045     );
2046     }
2047    
2048 root 1.18 $buttons->add (new DC::UI::Label text => " ");
2049 root 1.1
2050 root 1.18 my @path = DC::Pod::full_path_of $node;
2051 root 1.1 pop @path; # drop current node
2052    
2053     for my $node (@path) {
2054 root 1.18 $buttons->add (new DC::UI::Button
2055 root 1.31 text => $node->[DC::Pod::N_KW][0],
2056 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
2057 root 1.1 on_activate => sub {
2058     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2059     $load_node->($node);
2060     },
2061     );
2062 root 1.18 $buttons->add (new DC::UI::Label text => "/");
2063 root 1.1 }
2064    
2065 root 1.31 $buttons->add (new DC::UI::Label text => $node->[DC::Pod::N_KW][0], padding_x => 4, padding_y => 4);
2066 root 1.1
2067     $curnode = $node;
2068    
2069     $viewer->clear;
2070 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
2071 root 1.1 $viewer->scroll_to ($para);
2072     };
2073    
2074 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
2075 root 1.1
2076 root 1.18 $DC::Pod::goto_document = sub {
2077 root 1.1 my (@path) = @_;
2078    
2079     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2080    
2081 root 1.18 $load_node->((DC::Pod::find @path)[0]);
2082 root 1.1 $win->show;
2083     };
2084    
2085     $win
2086     }
2087    
2088     sub open_string_query {
2089     my ($title, $cb, $txt, $tooltip) = @_;
2090 root 1.18 my $dialog = new DC::UI::Toplevel
2091 root 1.1 x => "center",
2092     y => "center",
2093     z => 50,
2094     force_w => $WIDTH * 4/5,
2095     title => $title;
2096    
2097     $dialog->add (
2098 root 1.18 my $e = new DC::UI::Entry
2099 root 1.1 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2100     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2101     tooltip => $tooltip
2102     );
2103    
2104     $e->grab_focus;
2105     $e->set_text ($txt) if $txt;
2106     $dialog->show;
2107     }
2108    
2109     sub open_quit_dialog {
2110     unless ($QUIT_DIALOG) {
2111 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2112 root 1.1 x => "center",
2113     y => "center",
2114     z => 50,
2115     title => "Really Quit?",
2116     on_key_down => sub {
2117     my ($dialog, $ev) = @_;
2118     $ev->{sym} == 27 and $dialog->hide;
2119     }
2120     ;
2121    
2122 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2123 root 1.1
2124 root 1.18 $vb->add (new DC::UI::Label
2125 root 1.1 text => "You should find a savebed and apply it first!",
2126     max_w => $WIDTH * 0.25,
2127     ellipsize => 0,
2128     );
2129 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2130     $hb->add (new DC::UI::Button
2131 root 1.1 text => "Ok",
2132     expand => 1,
2133     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2134     );
2135 root 1.18 $hb->add (new DC::UI::Button
2136 root 1.1 text => "Quit anyway",
2137     expand => 1,
2138 root 1.4 on_activate => sub { EV::unloop EV::UNLOOP_ALL },
2139 root 1.1 );
2140     }
2141    
2142     $QUIT_DIALOG->show;
2143     $QUIT_DIALOG->grab_focus;
2144     }
2145    
2146     sub show_tip_of_the_day {
2147     # find all tips
2148 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2149 root 1.1
2150 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2151 root 1.1 my ($todindex) = @_;
2152     $todindex = 0 if $todindex >= @tod;
2153 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2154 root 1.1
2155     # create dialog
2156     my $dialog;
2157    
2158     my $close = sub {
2159     $dialog->destroy;
2160     };
2161    
2162 root 1.18 $dialog = new DC::UI::Toplevel
2163 root 1.1 x => "center",
2164     y => "center",
2165     z => 3,
2166     name => 'tip_of_the_day',
2167     force_w => int $WIDTH * 4/9,
2168     force_h => int $WIDTH * 2/9,
2169     title => "Tip of the day #" . (1 + $todindex),
2170 root 1.18 child => my $vbox = new DC::UI::VBox,
2171 root 1.1 has_close_button => 1,
2172     on_delete => $close,
2173     ;
2174    
2175 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2176 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2177 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2178 root 1.1
2179 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2180 root 1.1
2181 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2182 root 1.1 text => "Close",
2183     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>.",
2184     on_activate => $close,
2185     );
2186    
2187 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2188 root 1.1 text => "Next",
2189     tooltip => "Show the next <b>Tip of the day</b>.",
2190     on_activate => sub {
2191     $close->();
2192     &show_tip_of_the_day;
2193     },
2194     );
2195    
2196     $dialog->show;
2197     };
2198     }
2199    
2200     sub sdl_init {
2201 root 1.52 DC::SDL_Init DC::SDL_INIT_AUDIO #| DC::SDL_NOPARACHUTE
2202 root 1.1 and die "SDL::Init failed!\n";
2203     }
2204    
2205     sub video_init {
2206 root 1.52 DC::SDL_InitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2207     $SDL_REINIT = 0;
2208    
2209     @SDL_MODES = DC::SDL_ListModes 8, $CFG->{disable_alpha} ? 0 : 8;
2210     @SDL_MODES = DC::SDL_ListModes 8, 8 unless @SDL_MODES;
2211     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2212     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2213    
2214     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2215    
2216 root 1.61 if (!defined $CFG->{sdl_mode} or $CFG->{sdl_mode} > $#SDL_MODES) {
2217     $CFG->{sdl_mode} = 0; # lowest resolution by default
2218    
2219     # now choose biggets mode <= 1024x768
2220     for (0 .. $#SDL_MODES) {
2221     if ($SDL_MODES[$_][0] * $SDL_MODES[$_][1] <= 1024 * 768) {
2222     $CFG->{sdl_mode} = $_;
2223     }
2224 root 1.52 }
2225     }
2226 root 1.1
2227     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2228    
2229     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2230     $FULLSCREEN = $CFG->{fullscreen};
2231     $FAST = $CFG->{fast};
2232    
2233 root 1.59 # due to mac os x braindamage, we simply retry with !fullscreen in case of an error
2234 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2235 root 1.59 or DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, !$FULLSCREEN
2236 root 1.18 or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2237 root 1.1
2238     $SDL_ACTIVE = 1;
2239     $LAST_REFRESH = time - 0.01;
2240    
2241 root 1.18 DC::OpenGL::init;
2242     DC::Macro::init;
2243 root 1.1
2244     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2245    
2246 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2247 root 1.1
2248     #############################################################################
2249    
2250     if ($DEBUG_STATUS) {
2251 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2252 root 1.1 } else {
2253     # create/configure the widgets
2254    
2255 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2256 root 1.1 my (undef, $ev) = @_;
2257    
2258 root 1.18 if (my @macros = DC::Macro::find $ev) {
2259     DC::Macro::execute $_ for @macros;
2260 root 1.1
2261     return 1;
2262     }
2263    
2264     0
2265     });
2266    
2267 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2268 root 1.1 padding => 0,
2269     z => 100,
2270     force_x => "max",
2271     force_y => 0;
2272     $DEBUG_STATUS->show;
2273    
2274 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2275 root 1.29
2276     $MODBOX = new DC::UI::Label
2277     can_events => 1,
2278     can_hover => 1,
2279     markup => "",
2280     align => 0,
2281     font => $FONT_FIXED,
2282 root 1.30 tooltip => "#modifier_box",
2283     tooltip_width => 0.67,
2284     ;
2285 root 1.29
2286     update_modbox;
2287 root 1.1
2288 root 1.18 (new DC::UI::Frame
2289 root 1.1 bg => [0, 0, 0, 0.4],
2290     force_x => 0,
2291     force_y => "max",
2292 root 1.29 child => (my $LR = new DC::UI::VBox),
2293 root 1.1 )->show;
2294    
2295 root 1.29 $LR->add ($STATUSBOX);
2296     $LR->add ($MODBOX);
2297     $LR->add (new DC::UI::Label
2298     align => 0,
2299     markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode",
2300     fontsize => 0.5,
2301     fg => [1, 1, 0, 0.7],
2302     );
2303    
2304 root 1.18 DC::UI::Toplevel->new (
2305 root 1.2 title => "Minimap",
2306 root 1.1 name => "mapmap",
2307     x => 0,
2308     y => $FONTSIZE + 8,
2309     border_bg => [1, 1, 1, 192/255],
2310     bg => [1, 1, 1, 0],
2311 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2312 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2313     ),
2314     )->show;
2315    
2316 root 1.18 $MAPWIDGET = new DC::MapWidget;
2317 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2318     my ($mapwidget, $preset) = @_;
2319    
2320 elmex 1.23 $MESSAGE_DIST->activate_console ($preset)
2321     if $MESSAGE_DIST;
2322 root 1.1 });
2323     $MAPWIDGET->show;
2324     $MAPWIDGET->grab_focus;
2325    
2326 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2327 root 1.1 command => { },
2328     tooltip => "#completer_help",
2329     ;
2330    
2331 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2332 root 1.1 title => "Setup",
2333     name => "setup_dialog",
2334     x => 'center',
2335     y => 'center',
2336     z => 2,
2337     force_w => $::WIDTH * 0.6,
2338     force_h => $::HEIGHT * 0.6,
2339     has_close_button => 1,
2340     ;
2341    
2342     $METASERVER = metaserver_dialog;
2343 root 1.39 # the name is changed to not conflict with the older name as users could have hidden it
2344 root 1.40 $MESSAGE_WINDOW = new DC::UI::Dockbar
2345     name => "message_window2",
2346     title => 'Messages',
2347     force_w => $::WIDTH * 0.6,
2348     force_h => $::HEIGHT * 0.25,
2349     ;
2350    
2351 elmex 1.23 $MESSAGE_DIST = new DC::MessageDistributor dockbar => $MESSAGE_WINDOW;
2352 root 1.1
2353 root 1.38 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1,
2354 root 1.18 filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2355 root 1.1
2356     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2357     "Configure the server to play on, your username and password.");
2358     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2359     "Configure other server related options.");
2360     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2361     "Configure various client-specific settings.");
2362     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2363     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2364     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2365     "Configure the use of audio, sound effects and background music.");
2366     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2367     "Lets you define, edit and delete key bindings."
2368     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2369     . "with nothing set and the recording started. After doing the actions you "
2370     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2371     . "After pressing the combo the binding will be saved automatically and the "
2372     . "binding editor closes");
2373     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2374     "Some debuggin' options. Do not ask.");
2375    
2376 root 1.18 $BUTTONBAR = new DC::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2377 root 1.1
2378 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2379 root 1.1 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2380    
2381 root 1.37 # $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2382     # tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2383 root 1.1
2384     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
2385    
2386 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Playerbook", other => player_window,
2387 root 1.1 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2388    
2389 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2390 root 1.1 text => "Save Config",
2391     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2392     on_activate => sub {
2393 root 1.18 $::CFG->{layout} = DC::UI::get_layout;
2394 root 1.35 DC::write_cfg;
2395 root 1.1 status "Configuration Saved";
2396     0
2397     },
2398     );
2399    
2400 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2401 root 1.1 tooltip => "View Documentation");
2402    
2403 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2404 root 1.1 text => "Quit",
2405     tooltip => "Terminates the program",
2406     on_activate => sub {
2407     if ($CONN) {
2408     open_quit_dialog;
2409     } else {
2410 root 1.4 EV::unloop EV::UNLOOP_ALL;
2411 root 1.1 }
2412     0
2413     },
2414     );
2415    
2416     $BUTTONBAR->show;
2417     $SETUP_DIALOG->show;
2418     $MESSAGE_WINDOW->show;
2419     }
2420    
2421 root 1.53 $MODE_SLIDER->set_range ([$CFG->{sdl_mode}, 0, $#SDL_MODES, 1, 1]);
2422     $MODE_SLIDER->emit (changed => $CFG->{sdl_mode});
2423    
2424     $CAVEAT_LABEL->set_text ("None :)");
2425 root 1.55 $CAVEAT_LABEL->set_text ("Software Rendering (very slow)")
2426 root 1.53 unless DC::SDL_GL_GetAttribute DC::SDL_GL_ACCELERATED_VISUAL;
2427    
2428 root 1.1 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2429     }
2430    
2431     sub video_shutdown {
2432 root 1.18 DC::OpenGL::shutdown;
2433 root 1.52 DC::SDL_QuitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2434 root 1.1
2435     undef $SDL_ACTIVE;
2436     }
2437    
2438     my %animate_object;
2439     my $animate_timer;
2440    
2441     my $fps = 9;
2442    
2443     sub force_refresh {
2444     if ($ENV{CFPLUS_DEBUG} & 4) {
2445     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2446     debug sprintf "%3.2f", $fps;
2447     }
2448    
2449 root 1.16 undef $WANT_REFRESH;
2450     $_[0]->stop;
2451 root 1.12
2452 root 1.18 $DC::UI::ROOT->draw;
2453     DC::SDL_GL_SwapBuffers;
2454 root 1.1 $LAST_REFRESH = $NOW;
2455     }
2456    
2457 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2458 root 1.1
2459 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2460     $NOW = EV::now;
2461 root 1.1
2462     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2463 root 1.18 for DC::poll_events;
2464 root 1.1
2465     if (%animate_object) {
2466     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2467 root 1.16 $WANT_REFRESH = 1;
2468 root 1.1 }
2469 root 1.16
2470     $want_refresh->start
2471     if $WANT_REFRESH;
2472 root 1.4 };
2473 root 1.1
2474     sub animation_start {
2475     my ($widget) = @_;
2476     $animate_object{$widget} = $widget;
2477     }
2478    
2479     sub animation_stop {
2480     my ($widget) = @_;
2481     delete $animate_object{$widget};
2482     }
2483    
2484     %SDL_CB = (
2485 root 1.18 DC::SDL_QUIT => sub {
2486 root 1.4 EV::unloop EV::UNLOOP_ALL;
2487 root 1.1 },
2488 root 1.18 DC::SDL_VIDEORESIZE => sub {
2489 root 1.1 },
2490 root 1.18 DC::SDL_VIDEOEXPOSE => sub {
2491     DC::UI::full_refresh;
2492 root 1.1 },
2493 root 1.18 DC::SDL_ACTIVEEVENT => sub {
2494 root 1.12 # not useful, as APPACTIVE includes only iconified state, not unmapped
2495 root 1.18 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2496     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2497     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2498     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2499     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2500 root 1.1 },
2501 root 1.18 DC::SDL_KEYDOWN => sub {
2502     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2503 root 1.1 # alt-enter
2504 root 1.52 video_shutdown;
2505 root 1.1 $FULLSCREEN_ENABLE->toggle;
2506     video_init;
2507     } else {
2508 root 1.29 &DC::UI::feed_sdl_key_down_event;
2509 root 1.1 }
2510 root 1.29 update_modbox;
2511     },
2512     DC::SDL_KEYUP => sub {
2513     &DC::UI::feed_sdl_key_up_event;
2514     update_modbox;
2515 root 1.1 },
2516 root 1.18 DC::SDL_MOUSEMOTION => \&DC::UI::feed_sdl_motion_event,
2517     DC::SDL_MOUSEBUTTONDOWN => \&DC::UI::feed_sdl_button_down_event,
2518     DC::SDL_MOUSEBUTTONUP => \&DC::UI::feed_sdl_button_up_event,
2519     DC::SDL_USEREVENT => sub {
2520 root 1.1 if ($_[0]{code} == 1) {
2521     audio_channel_finished $_[0]{data1};
2522     } elsif ($_[0]{code} == 0) {
2523     audio_music_finished;
2524     }
2525     },
2526     );
2527    
2528     #############################################################################
2529    
2530 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2531     EV::unloop;
2532     #d# TODO calling exit here hangs the process in some futex
2533     };
2534 root 1.1
2535 root 1.59 # due to mac os x + sdl combined briandamage, we need this contortion
2536     sub main {
2537     {
2538     DC::Pod::load_docwiki DC::find_rcfile "docwiki.pst";
2539 root 1.34
2540 root 1.59 if (-e "$Deliantra::VARDIR/client.cf") {
2541     DC::read_cfg "$Deliantra::VARDIR/client.cf";
2542     } else {
2543     #TODO: compatibility cruft
2544     DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2545     print STDERR "INFO: used old configuration file\n";
2546     }
2547 root 1.15
2548 root 1.59 DC::DB::Server::run;
2549 root 1.35
2550 root 1.59 if ($CFG->{db_schema} < 1) {
2551     warn "INFO: upgrading database schema from 0 to 1, mapcache and tilecache will be lost\n";
2552     DC::DB::nuke_db;
2553     $CFG->{db_schema} = 1;
2554     DC::write_cfg;
2555     }
2556 root 1.35
2557 root 1.59 DC::DB::open_db;
2558 root 1.1
2559 root 1.59 DC::UI::set_layout ($::CFG->{layout});
2560 root 1.1
2561 root 1.59 my %DEF_CFG = (
2562 root 1.61 sdl_mode => undef,
2563 root 1.59 fullscreen => 1,
2564     fast => 0,
2565     force_opengl11 => undef,
2566     disable_alpha => 0,
2567     smooth_movement => 1,
2568     texture_compression => 1,
2569     map_scale => 1,
2570     fow_enable => 1,
2571     fow_intensity => 0,
2572     map_smoothing => 1,
2573     gui_fontsize => 1,
2574     log_fontsize => 0.7,
2575     gauge_fontsize => 1,
2576     gauge_size => 0.35,
2577     stat_fontsize => 0.7,
2578     mapsize => 100,
2579     audio_enable => 1,
2580     audio_hw_channels => 0,
2581     audio_hw_frequency => 0,
2582     audio_hw_chunksize => 0,
2583     audio_mix_channels => 8,
2584     effects_enable => 1,
2585     effects_volume => 1,
2586     bgm_enable => 1,
2587     bgm_volume => 0.5,
2588     output_rate => "",
2589     pickup => 0,
2590     inv_sort => "mtime",
2591     default => "profile", # default profile
2592     show_tips => 1,
2593     logview_max_par => 1000,
2594     shift_fire_stop => 0,
2595     );
2596    
2597     while (my ($k, $v) = each %DEF_CFG) {
2598     $CFG->{$k} = $v unless exists $CFG->{$k};
2599     }
2600 root 1.1
2601 root 1.59 $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2602     $PROFILE = $CFG->{profile}{default};
2603 root 1.1
2604 root 1.59 # convert old bindings (only default profile matters)
2605     if (my $bindings = delete $PROFILE->{bindings}) {
2606     while (my ($mod, $syms) = each %$bindings) {
2607     while (my ($sym, $cmds) = each %$syms) {
2608     push @{ $PROFILE->{macro} }, {
2609     accelkey => [$mod*1, $sym*1],
2610     action => $cmds,
2611     };
2612     }
2613 root 1.1 }
2614     }
2615    
2616 root 1.59 sdl_init;
2617 root 1.1
2618 root 1.59 {
2619     my @fonts = map DC::find_rcfile "fonts/$_", qw(
2620     DejaVuSans.ttf
2621     DejaVuSansMono.ttf
2622     DejaVuSans-Bold.ttf
2623     DejaVuSansMono-Bold.ttf
2624     DejaVuSans-Oblique.ttf
2625     DejaVuSansMono-Oblique.ttf
2626     DejaVuSans-BoldOblique.ttf
2627     DejaVuSansMono-BoldOblique.ttf
2628     );
2629    
2630     DC::add_font $_ for @fonts;
2631    
2632     $FONT_PROP = new_from_file DC::Font $fonts[0];
2633     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2634 root 1.1
2635 root 1.59 $FONT_PROP->make_default;
2636 root 1.30
2637 root 1.59 DC::pango_init;
2638     }
2639 root 1.1
2640     # compare mono (ft) vs. rgba (cairo)
2641     # ft - 1.8s, cairo 3s, even in alpha-only mode
2642     # for my $rgba (0..1) {
2643     # my $t1 = Time::HiRes::time;
2644     # for (1..1000) {
2645 root 1.18 # my $layout = DC::Layout->new ($rgba);
2646 root 1.1 # $layout->set_text ("hallo" x 100);
2647     # $layout->render;
2648     # }
2649     # my $t2 = Time::HiRes::time;
2650     # warn $t2-$t1;
2651     # }
2652    
2653 root 1.59 video_init;
2654     audio_init;
2655     }
2656 root 1.1
2657 root 1.59 show_tip_of_the_day if $CFG->{show_tips};
2658 root 1.1
2659 root 1.59 our $STARTUP_CANCEL = EV::idle sub {
2660     undef $::STARTUP_CANCEL;
2661     $startup_done->();
2662     };
2663 root 1.1
2664 root 1.59 delete $SIG{__DIE__};
2665     EV::loop;
2666 root 1.1
2667     #video_shutdown;
2668     #audio_shutdown;
2669 root 1.59 DC::OpenGL::quit;
2670     DC::SDL_Quit;
2671     DC::DB::Server::stop;
2672     }
2673    
2674     DC::SDL_braino; # see sub above
2675 root 1.1
2676     =head1 NAME
2677    
2678     deliantra - A Deliantra MORPG game client
2679    
2680     =head1 SYNOPSIS
2681    
2682     Just run it - no commandline arguments are supported.
2683    
2684     =head1 USAGE
2685    
2686     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2687     be used in fullscreen mode and interactively.
2688    
2689     =head1 DEBUGGING
2690    
2691    
2692     CFPLUS_DEBUG - environment variable
2693    
2694     1 draw borders around widgets
2695     2 add low-level widget info to tooltips
2696     4 show fps
2697     8 suppress tooltips
2698    
2699     =head1 AUTHOR
2700    
2701 root 1.57 Marc Lehmann <deliantra@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2702 root 1.1
2703    
2704