ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.10
Committed: Fri Jan 6 05:02:39 2012 UTC (12 years, 5 months ago) by root
Branch: MAIN
Changes since 1.9: +3 -3 lines
Log Message:
*** empty log message ***

File Contents

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