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