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