ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.80
Committed: Fri Dec 19 22:19:52 2008 UTC (15 years, 5 months ago) by root
Branch: MAIN
Changes since 1.79: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

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