ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.52
Committed: Fri Jul 18 21:18:42 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.51: +41 -14 lines
Log Message:
*** empty log message ***

File Contents

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