ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.3
Committed: Thu Dec 29 07:13:44 2011 UTC (12 years, 4 months ago) by root
Branch: MAIN
Changes since 1.2: +6 -3 lines
Log Message:
*** empty log message ***

File Contents

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