ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.37
Committed: Mon May 5 16:19:19 2008 UTC (16 years ago) by root
Branch: MAIN
Changes since 1.36: +5 -5 lines
Log Message:
*** empty log message ***

File Contents

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