ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.43
Committed: Wed May 21 23:35:55 2008 UTC (16 years ago) by root
Branch: MAIN
CVS Tags: rel-0_9972
Changes since 1.42: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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