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