ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.28
Committed: Wed Dec 24 06:12:21 2014 UTC (9 years, 4 months ago) by root
Branch: MAIN
Changes since 1.27: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

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