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