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