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