ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.15
Committed: Wed Jan 18 00:51:23 2012 UTC (12 years, 4 months ago) by root
Branch: MAIN
Changes since 1.14: +4 -63 lines
Log Message:
*** empty log message ***

File Contents

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