ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.83
Committed: Sun Jan 4 10:22:19 2009 UTC (15 years, 5 months ago) by root
Branch: MAIN
Changes since 1.82: +6 -1 lines
Log Message:
*** empty log message ***

File Contents

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