ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.13
Committed: Sat Jan 7 19:43:46 2012 UTC (12 years, 5 months ago) by root
Branch: MAIN
Changes since 1.12: +1 -1 lines
Log Message:
ugh

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