ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.49
Committed: Mon Jul 7 12:56:07 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.48: +0 -5 lines
Log Message:
*** empty log message ***

File Contents

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