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