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