ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.47
Committed: Mon Jul 7 05:02:03 2008 UTC (15 years, 11 months ago) by root
Branch: MAIN
Changes since 1.46: +4 -0 lines
Log Message:
*** empty log message ***

File Contents

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