ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.31
Committed: Tue Mar 25 02:12:35 2008 UTC (16 years, 2 months ago) by root
Branch: MAIN
Changes since 1.30: +2 -2 lines
Log Message:
also use arrays for nodes also for, among other things, speed and memory savings

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