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