ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.5
Committed: Fri Dec 30 10:59:00 2011 UTC (12 years, 4 months ago) by root
Branch: MAIN
Changes since 1.4: +4 -4 lines
Log Message:
*** empty log message ***

File Contents

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