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