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