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