ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.12
Committed: Sat Jan 7 19:29:58 2012 UTC (12 years, 5 months ago) by root
Branch: MAIN
Changes since 1.11: +1 -0 lines
Log Message:
ugh

File Contents

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