ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.42
Committed: Thu May 8 18:25:25 2008 UTC (16 years ago) by elmex
Branch: MAIN
Changes since 1.41: +1 -0 lines
Log Message:
added default value for shift_fire_stop

File Contents

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