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