ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.47
Committed: Mon Jul 7 05:02:03 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.46: +4 -0 lines
Log Message:
*** empty log message ***

File Contents

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