ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.10
Committed: Fri Jan 6 05:02:39 2012 UTC (12 years, 5 months ago) by root
Branch: MAIN
Changes since 1.9: +3 -3 lines
Log Message:
*** empty log message ***

File Contents

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