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