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