ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.48
Committed: Mon Jul 7 08:02:17 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
CVS Tags: rel-0_9973
Changes since 1.47: +1 -0 lines
Log Message:
0.9973

File Contents

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