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