ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC/Main.pm
Revision: 1.1
Committed: Tue Dec 27 07:23:33 2011 UTC (12 years, 5 months ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

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