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