ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.71
Committed: Mon Sep 1 15:19:07 2008 UTC (15 years, 8 months ago) by root
Branch: MAIN
Changes since 1.70: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

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