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