ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.31
Committed: Mon Nov 19 00:56:08 2018 UTC (5 years, 6 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.30: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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