ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.41
Committed: Mon May 5 20:51:36 2008 UTC (16 years ago) by elmex
Branch: MAIN
CVS Tags: rel-0_9971
Changes since 1.40: +3 -1 lines
Log Message:
fixed a small issue with the sort order

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.18 new DC::Protocol
744 root 1.1 host => $host,
745     port => $port || 13327,
746     user => $PROFILE->{user},
747     pass => $PROFILE->{password},
748     mapw => $mapsize,
749     maph => $mapsize,
750    
751 root 1.26 client => "$DC::VERSION $] $^O",
752 root 1.1
753     map_widget => $MAPWIDGET,
754     statusbox => $STATUSBOX,
755     map => $MAP,
756     mapmap => $MAPMAP,
757     query => \&server_query,
758    
759     setup_req => {
760     smoothing => $CFG->{map_smoothing}*1,
761     },
762     };
763    
764     if ($CONN) {
765 root 1.18 DC::lowdelay fileno $CONN->{fh};
766 root 1.1
767     status "login successful";
768     } else {
769 root 1.32 warn $@;
770 root 1.1 status "unable to connect";
771     stop_game();
772     }
773     }
774    
775     sub stop_game {
776     $LOGIN_BUTTON->set_text ("Login / Register");
777     $SETUP_NOTEBOOK->set_current_page ($SETUP_LOGIN);
778     $SETUP_DIALOG->show;
779     $PL_WINDOW->hide;
780     $SPELL_LIST->clear_spells;
781 root 1.18 $DC::UI::ROOT->emit (stop_game => ! ! $CONN);
782 root 1.1
783     &audio_music_set_ambient ([]);
784    
785     return unless $CONN;
786    
787     status "connection closed";
788    
789     destroy_query_dialog $CONN;
790     $CONN->destroy;
791     $CONN = 0; # false, does not autovivify
792    
793     undef $MAP;
794     }
795    
796     sub graphics_setup {
797 root 1.18 my $vbox = new DC::UI::VBox;
798 root 1.1
799 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]);
800 root 1.1
801     my $row = 0;
802    
803 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "OpenGL Info");
804     $table->add_at (1, $row++, new DC::UI::Label fontsize => 0.8, text => DC::OpenGL::gl_vendor . ", " . DC::OpenGL::gl_version,
805 root 1.1 can_events => 1,
806 root 1.18 tooltip => "<tt><span size='8192'>" . (DC::OpenGL::gl_extensions) . "</span></tt>");
807 root 1.1
808     my $vidmode_tooltip =
809     "<b>Video Mode.</b> The video mode to use for fullscreen (and the window size for windowed operation). "
810     . "The format is <i>width</i> x <i>height</i> \@ <i>depth-per-channel</i> + <i>alpha-channel</i>.";
811    
812 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Video Mode");
813 root 1.18 $table->add_at (1, $row++, my $hbox = new DC::UI::HBox);
814 root 1.1
815 root 1.18 $hbox->add (my $mode_slider = new DC::UI::Slider
816 root 1.1 force_w => $WIDTH * 0.1, expand => 1, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1],
817     tooltip => $vidmode_tooltip);
818 root 1.18 $hbox->add (my $mode_label = new DC::UI::Label
819 root 1.22 height => 0.8, template => "9999x9999@9+9",
820 root 1.1 can_events => 1, tooltip => $vidmode_tooltip);
821    
822     $mode_slider->connect (changed => sub {
823     my ($self, $value) = @_;
824    
825     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
826     $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
827     });
828     $mode_slider->emit (changed => $mode_slider->{range}[0]);
829    
830 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fullscreen");
831 root 1.18 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new DC::UI::CheckBox
832 root 1.1 state => $CFG->{fullscreen},
833     tooltip => "Bring the client into fullscreen mode.",
834     on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
835     );
836    
837 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Force OpenGL 1.1");
838 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
839 root 1.1 state => $CFG->{force_opengl11},
840 elmex 1.23 tooltip => "Limit Deliantra to use OpenGL 1.1 features only. This will normally result in "
841 root 1.1 . "higher memory usage and slower performance. It will, however, help tremendously on "
842     . "cards that claim to support a feature but fall back to software rendering. "
843     . "Nvidia Geforce FX cards are known to claim features the hardware doesn't support, "
844     . "but cards and drivers from other vendors (ATI) are often just as bad. <b>If you "
845     . "experience extremely low framerates and your card should do better, try this option.</b>",
846     on_changed => sub { my ($self, $value) = @_; $CFG->{force_opengl11} = $value; 0 }
847     );
848    
849 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Compress Textures");
850 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
851 root 1.1 state => $CFG->{texture_compression},
852     tooltip => "Use texture compression. Normally this will not reduce visual quality noticable but "
853     . "will save a lot of memory and increase performance. The compression algorithm "
854     . "can differ form card to card, so your mileage may vary. This setting is ignored in "
855     . "forced OpenGL 1.1 mode.",
856     on_changed => sub { my ($self, $value) = @_; $CFG->{texture_compression} = $value; 0 }
857     );
858    
859 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fast & Ugly");
860 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
861 root 1.1 state => $CFG->{fast},
862     tooltip => "Lower the visual quality considerably to speed up rendering.",
863     on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
864     );
865    
866 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "GUI Fontsize");
867 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
868 root 1.1 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
869     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
870     on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
871     );
872    
873 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
874 root 1.22 expand => 1, text => "Apply",
875 root 1.1 tooltip => "Apply the video settings above.",
876     on_activate => sub {
877     video_shutdown ();
878     video_init ();
879     0
880     }
881     );
882    
883 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Scale");
884 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
885 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
886     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
887     on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
888     );
889    
890 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Smoothing");
891 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
892 root 1.1 state => $CFG->{map_smoothing},
893     tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
894     . "This increases load on the graphics subsystem and works only with TRT servers. "
895     . "Changes take effect at next login only.",
896     on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
897     );
898    
899 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fog of War");
900 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
901 root 1.1 state => $CFG->{fow_enable},
902     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
903     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
904     );
905    
906 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "FoW Intensity");
907 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
908 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
909     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
910     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
911     );
912    
913 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Fontsize");
914 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
915 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
916     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant, "
917     . "but you still need to press apply to correctly re-layout the widget.",
918 elmex 1.23 on_changed => sub { $MESSAGE_DIST->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
919 root 1.1 );
920    
921 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge fontsize");
922 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
923 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
924     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
925     on_changed => sub {
926     $CFG->{gauge_fontsize} = $_[1];
927     &set_gauge_window_fontsize;
928     0
929     }
930     );
931    
932 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge size");
933 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
934 root 1.1 range => [$CFG->{gauge_size}, 0.2, 0.8],
935     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
936     on_changed => sub {
937     $CFG->{gauge_size} = $_[1];
938     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
939     0
940     }
941     );
942    
943     $vbox
944     }
945    
946     our $AUDIO_HW_CHUNKSIZE;
947     our $AUDIO_INFO;
948    
949     sub audio_tab_update {
950 root 1.18 my ($freq, $format, $chans) = DC::Mix_QuerySpec;
951 root 1.1
952     $AUDIO_HW_CHUNKSIZE->set_options ([
953     [0, "default", "Use System Default"],
954     map {
955     my $ms = sprintf "%dms", 1000 * $_ / ($CFG->{audio_hw_frequency} || 22050);
956     [$_, $ms, "$ms ($_ samples)"],
957     } 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
958     ]);
959    
960     my $text = !$freq
961     ? "audio is off"
962     : "audio is enabled\n"
963     . "frequency (Hz): $freq\n"
964     . "channels: $chans";
965    
966     $AUDIO_INFO->set_text ($text);
967     }
968    
969     sub audio_setup {
970 root 1.18 my $vbox = new DC::UI::VBox;
971 root 1.1
972 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 0, 1]);
973 root 1.1
974     my $row = 0;
975    
976 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Audio Enable");
977 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
978 root 1.1 state => $CFG->{audio_enable},
979     tooltip => "<b>Master Audio Enable.</b> If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
980     on_changed => sub { $CFG->{audio_enable} = $_[1]; 1 }
981     );
982    
983 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Sound Effects");
984 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
985 root 1.1 expand => 1, state => $CFG->{effects_enable},
986     tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
987     on_changed => sub {
988     $CFG->{effects_enable} = $_[1];
989     $CONN->update_fx_want if $CONN;
990     1
991     }
992     );
993 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
994 root 1.1 expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
995     tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
996     . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
997     on_changed => sub { $CFG->{effects_volume} = $_[1]; 1 }
998     );
999    
1000 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Background Music");
1001 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
1002 root 1.1 expand => 1, state => $CFG->{bgm_enable},
1003     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
1004     on_changed => sub {
1005     $CFG->{bgm_enable} = $_[1];
1006     $CONN->update_fx_want if $CONN;
1007     audio_music_push;
1008     1
1009     }
1010     );
1011 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
1012 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
1013     tooltip => "The volume of the background music. Changes are instant.",
1014     on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
1015     );
1016    
1017 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Frequency");
1018 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1019 root 1.1 c_colspan => 2, expand => 1,
1020     value => $CFG->{audio_hw_frequency},
1021     options => [
1022     [ 0, "default" , "Use System Default"],
1023     [11025, "11 kHz" , "11kHz (low quality)"],
1024     [22050, "22 kHz" , "22kHz (reduced quality)"],
1025     [44100, "44.1 kHz", "44.1kHz (cd quality)"],
1026     [48000, "48 kHz" , "48kHz (studio quality)"],
1027     ],
1028     tooltip => "The sampling frequency to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1029     on_changed => sub {
1030     $CFG->{audio_hw_frequency} = $_[1];
1031     audio_tab_update;
1032     1
1033     }
1034     );
1035    
1036 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Channels");
1037 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1038 root 1.1 c_colspan => 2, expand => 1,
1039     value => $CFG->{audio_hw_channels},
1040     options => [
1041     [0, "default" , "Use System Default"],
1042     [1, "Mono" , "Mono (single channel, low quality)"],
1043 root 1.6 [2, "Stereo" , "Stereo (dual channel, standard quality)"],
1044 root 1.1 [4, "4 Ch Surround", "4 Channel Surround Sound (3d sound, high quality)"],
1045     [6, "6 Ch Surround", "6 Channel Surround Sound (3d sound + center + lfe)"],
1046     ],
1047     tooltip => "The number of independent sound channels to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1048     on_changed => sub {
1049     $CFG->{audio_hw_channels} = $_[1];
1050     audio_tab_update;
1051     1
1052     }
1053     );
1054    
1055 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Latency");
1056 root 1.18 $table->add_at (1, $row++, $AUDIO_HW_CHUNKSIZE = new DC::UI::Selector
1057 root 1.1 c_colspan => 2, expand => 1,
1058     value => $CFG->{audio_hw_chunksize},
1059     tooltip => "The guarenteed latency. Lower is better, but also more cpu-intensive and might cause stuttering. If music playback "
1060     . "is stuttering, increase this value. Values of 50-100ms are optimal.",
1061     on_changed => sub {
1062     $CFG->{audio_hw_chunksize} = $_[1];
1063     audio_tab_update;
1064     1
1065     }
1066     );
1067    
1068     # should really be a slider
1069 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Mixer Voices");
1070 root 1.18 $table->add_at (1, $row++, new DC::UI::ValSlider
1071 root 1.1 c_colspan => 2, expand => 1,
1072     tooltip => "The number of simultaneous sound effects possible. Higher is better, but also more cpu-intensive and might cause stuttering.",
1073     range => [$::CFG->{audio_mix_channels}, 4, 32, 0, 1],
1074     template => ">= 99",
1075     on_changed => sub {
1076     my ($slider, $value) = @_;
1077    
1078     $CFG->{audio_mix_channels} = $value
1079     if $value;
1080     1;
1081     }
1082     );
1083    
1084 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
1085 root 1.22 c_colspan => 2, expand => 1, text => "Apply",
1086 root 1.1 tooltip => "Apply the audio settings",
1087     on_activate => sub {
1088     audio_shutdown ();
1089     audio_init ();
1090     0
1091     }
1092     );
1093    
1094 root 1.18 $vbox->add (new DC::UI::FancyFrame
1095 root 1.1 expand => 1,
1096     label => "Audio Info",
1097 root 1.18 child => ($AUDIO_INFO = new DC::UI::Label ellipsise => 0),
1098 root 1.1 );
1099    
1100     audio_tab_update;
1101    
1102     $vbox
1103     }
1104    
1105     sub set_gauge_window_fontsize {
1106     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1107     $_->set_fontsize ($::CFG->{gauge_fontsize});
1108     }
1109     }
1110    
1111     sub make_gauge_window {
1112     my $gh = int $HEIGHT * $CFG->{gauge_size};
1113    
1114 root 1.18 my $win = new DC::UI::Frame (
1115 root 1.1 force_x => 0,
1116     force_y => "max",
1117     force_w => $WIDTH,
1118     force_h => $gh,
1119     );
1120    
1121 root 1.18 $win->add (my $hbox = new DC::UI::HBox
1122 root 1.1 children => [
1123 root 1.18 (new DC::UI::HBox expand => 1),
1124     (new DC::UI::VBox children => [
1125     (new DC::UI::Empty expand => 1),
1126     (new DC::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new DC::UI::Table)),
1127 root 1.1 ]),
1128 root 1.18 (my $vbox = new DC::UI::VBox),
1129 root 1.1 ],
1130     );
1131    
1132 root 1.18 $vbox->add (new DC::UI::HBox
1133 root 1.1 expand => 1,
1134     children => [
1135 root 1.18 (new DC::UI::Empty expand => 1),
1136     (my $hb = new DC::UI::HBox),
1137 root 1.1 ],
1138     );
1139    
1140 root 1.18 $hb->add (my $hg = new DC::UI::Gauge type => 'hp', tooltip => "#stat_health");
1141     $hb->add (my $mg = new DC::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1142     $hb->add (my $gg = new DC::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1143     $hb->add (my $fg = new DC::UI::Gauge type => 'food', tooltip => "#stat_food");
1144    
1145 root 1.22 $vbox->add (my $exp = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1146 root 1.18 $vbox->add (my $prg = new DC::UI::ExperienceProgress);
1147     $vbox->add (my $sklprg = new DC::UI::ExperienceProgress);
1148 root 1.22 $vbox->add (my $rng = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1149 root 1.1
1150     $GAUGES = {
1151     exp => $exp, prg => $prg, sklprg => $sklprg,
1152     win => $win, range => $rng,
1153     hp => $hg, mana => $mg, grace => $gg, food => $fg,
1154     };
1155    
1156     &set_gauge_window_fontsize;
1157    
1158     $win
1159     }
1160    
1161     sub debug_setup {
1162 root 1.18 my $table = new DC::UI::Table;
1163 root 1.1
1164 root 1.18 $table->add_at (0, 0, new DC::UI::Label text => "Widget Borders");
1165     $table->add_at (1, 0, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1166     $table->add_at (0, 1, new DC::UI::Label text => "Tooltip Widget Info");
1167     $table->add_at (1, 1, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1168     $table->add_at (0, 2, new DC::UI::Label text => "Show FPS");
1169     $table->add_at (1, 2, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1170     $table->add_at (0, 3, new DC::UI::Label text => "Suppress Tooltips");
1171     $table->add_at (1, 3, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1172     $table->add_at (0, 4, new DC::UI::Button text => "die on click(tm)", on_activate => sub { &DC::debug() } );
1173    
1174 root 1.21 $table->add_at (0, 5, new DC::UI::TextEdit text => "line1\0152\0153\nµikachu\nづx゙つ゛");#d#
1175 root 1.18
1176     $table->add_at (7,7, my $t = new DC::UI::Table expand => 0);
1177 root 1.20 $t->add_at (0,0, new DC::UI::Label text => "a a", c_rowspan => 1, c_colspan => 2);
1178     $t->add_at (2,0, new DC::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1179     $t->add_at (1,2, new DC::UI::Label text => "c c", c_rowspan => 1, c_colspan => 2);
1180     $t->add_at (0,1, new DC::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1181 root 1.18 $t->add_at (1,1, new DC::UI::Label text => "e");
1182 root 1.1
1183 root 1.18 $table->add_at (7, 6, my $c = new DC::UI::Canvas);
1184 root 1.1
1185     $c->add_items ({
1186     type => "line_loop",
1187     color => [0, 1, 0],
1188     width => 9,
1189     coord_mode => "abs",
1190     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1191     });
1192    
1193     $c->add_items ({
1194     type => "lines",
1195     color => [1, 1, 0],
1196     width => 2,
1197     coord_mode => "rel",
1198     coord => [[0,0], [1,1], [1,0], [0,1]],
1199     });
1200    
1201     $c->add_items ({
1202     type => "polygon",
1203     color => [0, 0.43, 0],
1204     width => 2,
1205     coord_mode => "rel",
1206     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1207     });
1208    
1209     $table
1210     }
1211    
1212     sub stats_window {
1213 root 1.18 my $r = new DC::UI::ScrolledWindow (
1214 root 1.1 expand => 1,
1215     scroll_y => 1
1216     );
1217 root 1.18 $r->add (my $vb = new DC::UI::VBox);
1218 root 1.1
1219 root 1.18 $vb->add (new DC::UI::FancyFrame
1220 root 1.1 label => "Player",
1221 root 1.18 child => (my $pi = new DC::UI::VBox),
1222 root 1.1 );
1223    
1224 root 1.22 $pi->add ($STATWIDS->{title} = new DC::UI::Label text => "Title:", expand => 1, align => 0,
1225 root 1.1 can_hover => 1, can_events => 1,
1226     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1227 root 1.22 $pi->add ($STATWIDS->{map} = new DC::UI::Label align => 0, text => "Map:", expand => 1,
1228 root 1.1 can_hover => 1, can_events => 1,
1229     tooltip => "The map you are currently on (if supported by the server).");
1230    
1231 root 1.18 $pi->add (my $hb0 = new DC::UI::HBox);
1232 root 1.22 $hb0->add ($STATWIDS->{weight} = new DC::UI::Label text => "Weight:", expand => 1, align => 0,
1233 root 1.1 can_hover => 1, can_events => 1,
1234     tooltip => "The weight of the player including all inventory items.");
1235 root 1.22 $hb0->add ($STATWIDS->{m_weight} = new DC::UI::Label align => 0, text => "Max weight:", expand => 1,
1236 root 1.1 can_hover => 1, can_events => 1,
1237     tooltip => "The weight limit: you cannot carry more than this.");
1238    
1239 root 1.18 $vb->add (new DC::UI::FancyFrame
1240 root 1.1 label => "Primary/Secondary Statistics",
1241 root 1.18 child => (my $hb = new DC::UI::HBox expand => 1),
1242 root 1.1 );
1243 root 1.18 $hb->add (my $tbl = new DC::UI::Table expand => 1);
1244 root 1.1
1245     my $color2 = [1, 1, 0];
1246    
1247     for (
1248     [0, 0, st_str => "Str", 30],
1249     [0, 1, st_dex => "Dex", 30],
1250     [0, 2, st_con => "Con", 30],
1251     [0, 3, st_int => "Int", 30],
1252     [0, 4, st_wis => "Wis", 30],
1253     [0, 5, st_pow => "Pow", 30],
1254     [0, 6, st_cha => "Cha", 30],
1255    
1256     [2, 0, st_wc => "Wc", -120],
1257     [2, 1, st_ac => "Ac", -120],
1258     [2, 2, st_dam => "Dam", 120],
1259     [2, 3, st_arm => "Arm", 120],
1260     [2, 4, st_spd => "Spd", 10.54],
1261     [2, 5, st_wspd => "WSp", 10.54],
1262     ) {
1263     my ($col, $row, $id, $label, $template) = @$_;
1264    
1265 root 1.18 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new DC::UI::Label
1266 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1,
1267     align => 1, template => $template, tooltip => "#stat_$label");
1268 root 1.18 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new DC::UI::Label
1269 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2,
1270     align => 0, text => $label, tooltip => "#stat_$label");
1271 root 1.1 }
1272    
1273 root 1.18 $vb->add (new DC::UI::FancyFrame
1274 root 1.1 label => "Resistancies",
1275 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]),
1276 root 1.1 );
1277    
1278     my $row = 0;
1279     my $col = 0;
1280    
1281     my %resist_names = (
1282     slow => ["Slow",
1283     "<b>Slow</b> (slows you down when you are hit by the spell. Monsters will have an opportunity to come near you faster and hit you more often.)"],
1284     holyw => ["Holy Word",
1285     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1286     conf => ["Confusion",
1287     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1288     fire => ["Fire",
1289     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1290     depl => ["Depletion",
1291     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1292     magic => ["Magic",
1293     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1294     drain => ["Draining",
1295     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1296     acid => ["Acid",
1297     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1298     pois => ["Poison",
1299     "<b>Poison</b> (resistance to getting poisoned)"],
1300     para => ["Paralysation",
1301     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1302     deat => ["Death",
1303     "<b>Death</b> (resistance against death spells)"],
1304     phys => ["Physical",
1305     "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat. The value displayed here is also displayed in the 'Arm' field on the left.)"],
1306     blind => ["Blind",
1307     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1308     fear => ["Fear",
1309     "<b>Fear</b> (this attack will drive you away from monsters who cast this and hit you successfully, being resistant to this helps a lot when fighting those monsters)"],
1310     tund => ["Turn undead",
1311     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1312     elec => ["Electricity",
1313     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1314     cold => ["Cold",
1315     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1316     ghit => ["Ghost hit",
1317     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1318     );
1319    
1320     for (qw/slow holyw conf fire depl magic
1321     drain acid pois para deat phys
1322     blind fear tund elec cold ghit/)
1323     {
1324 root 1.22 $tbl2->add_at ($col + 2, $row,
1325 root 1.1 $STATWIDS->{"res_$_"} =
1326 root 1.18 new DC::UI::Label
1327 root 1.1 font => $FONT_FIXED,
1328     template => "-100%",
1329 root 1.22 align => 1,
1330 root 1.1 can_events => 1,
1331     can_hover => 1,
1332     tooltip => $resist_names{$_}->[1],
1333     );
1334 root 1.18 $tbl2->add_at ($col + 1, $row, new DC::UI::Image
1335 root 1.1 font => $FONT_FIXED,
1336     can_hover => 1,
1337     can_events => 1,
1338     path => "ui/resist/resist_$_.png",
1339     tooltip => $resist_names{$_}->[1],
1340     );
1341 root 1.22 $tbl2->add_at ($col + 0, $row, new DC::UI::Label
1342 root 1.1 text => $resist_names{$_}->[0],
1343     font => $FONT_FIXED,
1344 root 1.22 align => 1,
1345 root 1.1 can_hover => 1,
1346     can_events => 1,
1347     tooltip => $resist_names{$_}->[1],
1348     );
1349    
1350     $row++;
1351     if ($row % 6 == 0) {
1352 root 1.22 $col += 4;
1353 root 1.1 $row = 0;
1354     }
1355     }
1356    
1357     #update_stats_window ({});
1358    
1359     $r
1360     }
1361    
1362     sub skill_window {
1363 root 1.18 my $sw = new DC::UI::ScrolledWindow (expand => 1);
1364 root 1.1
1365 root 1.18 $sw->add ($STATWIDS->{skill_tbl} = new DC::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1366 root 1.1
1367     $sw
1368     }
1369    
1370     sub formsep($) {
1371     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1372     }
1373    
1374     my $METASERVER_ATIME;
1375    
1376     sub update_metaserver {
1377     my ($metaserver_dialog) = @_;
1378    
1379     $METASERVER = $metaserver_dialog
1380     if defined $metaserver_dialog;
1381    
1382     return if $METASERVER_ATIME > time;
1383     $METASERVER_ATIME = time + 60;
1384    
1385     my $table = $METASERVER->{table};
1386     $table->clear;
1387 root 1.18 $table->add_at (0, 0, my $label = new DC::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1388 root 1.1
1389     my $ok = 0;
1390    
1391 root 1.18 DC::background {
1392     my $ua = DC::lwp_useragent;
1393 root 1.1
1394 root 1.18 DC::background_msg DC::decode_json +(DC::lwp_check $ua->get ($META_SERVER))->decoded_content;
1395 root 1.1 } sub {
1396     my ($msg) = @_;
1397     if ($msg) {
1398     $table->clear;
1399    
1400     my @tip = (
1401     "The current number of users logged in on the server.",
1402     "The hostname of the server.",
1403     "The time this server has been running without being restarted.",
1404     "Short information about this server provided by its admins.",
1405     );
1406     my @col = qw(#Users Host Uptime Version Description);
1407 root 1.18 $table->add_at ($_, 0, new DC::UI::Label
1408 root 1.22 can_hover => 1, can_events => 1, fg => [1, 1, 0],
1409 root 1.1 text => $col[$_], tooltip => $tip[$_])
1410     for 0 .. $#col;
1411    
1412 root 1.22 my @align = qw(1 0.5 1 1 0);
1413 root 1.1
1414     my $y = 0;
1415     for my $m (@{ $msg->{servers} }) {
1416     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1417     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1418    
1419     for ($desc) {
1420     s/<br>/\n/gi;
1421     s/<li>/\n· /gi;
1422     s/<.*?>//sgi;
1423     s/&amp;/&/g;
1424     s/&lt;/</g;
1425     s/&gt;/>/g;
1426     }
1427    
1428     $uptime = sprintf "%dd %02d:%02d:%02d",
1429     (int $uptime / 86400),
1430     (int $uptime / 3600) % 24,
1431     (int $uptime / 60) % 60,
1432     $uptime % 60;
1433    
1434     $m = [$users, $host, $uptime, $version, $desc];
1435    
1436     $y++;
1437    
1438 root 1.18 $table->add_at (scalar @$m, $y, new DC::UI::VBox children => [
1439     (new DC::UI::Button
1440 root 1.1 text => "Use",
1441     tooltip => "Put this server into the <b>Host:Port</b> field",
1442     on_activate => sub {
1443     $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1444     $METASERVER->hide;
1445     0
1446     },
1447     ),
1448 root 1.18 (new DC::UI::Empty expand => 1),
1449 root 1.1 ]);
1450    
1451 root 1.18 $table->add_at ($_, $y, new DC::UI::Label
1452 root 1.1 max_w => $::WIDTH * 0.4,
1453     ellipsise => 0,
1454     align => $align[$_],
1455     text => $m->[$_],
1456     tooltip => $tip[$_],
1457     fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1458     can_hover => 1,
1459     can_events => 1,
1460     fontsize => 0.8)
1461     for 0 .. $#$m;
1462     }
1463     } else {
1464     $ok or $label->set_text ("error while contacting metaserver");
1465     }
1466     };
1467    
1468     }
1469    
1470     sub metaserver_dialog {
1471 root 1.18 my $vbox = new DC::UI::VBox;
1472     my $table = new DC::UI::Table;
1473     $vbox->add (new DC::UI::ScrolledWindow expand => 1, child => $table);
1474 root 1.1
1475 root 1.18 my $dialog = new DC::UI::Toplevel
1476 root 1.1 title => "Server List",
1477     name => 'metaserver_dialog',
1478     x => 'center',
1479     y => 'center',
1480     z => 3,
1481     force_w => $::WIDTH * 0.9,
1482     force_h => $::HEIGHT * 0.7,
1483     child => $vbox,
1484     has_close_button => 1,
1485     table => $table,
1486     on_visibility_change => sub {
1487     update_metaserver ($_[0]) if $_[1];
1488     0
1489     },
1490     ;
1491    
1492     $dialog
1493     }
1494    
1495     sub login_setup {
1496 root 1.18 my $vbox = new DC::UI::VBox;
1497 root 1.1
1498 root 1.18 $vbox->add (new DC::UI::FancyFrame
1499 root 1.1 label => "Login Settings",
1500 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1501 root 1.1 );
1502    
1503 root 1.22 $table->add_at (0, 4, new DC::UI::Label align => 1, text => "Username");
1504 root 1.18 $table->add_at (1, 4, new DC::UI::Entry
1505 root 1.1 text => $CFG->{profile}{default}{user},
1506     tooltip => "The name of your character on the server.",
1507     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1508     );
1509    
1510 root 1.22 $table->add_at (0, 5, new DC::UI::Label align => 1, text => "Password");
1511 root 1.18 $table->add_at (1, 5, new DC::UI::Entry
1512 root 1.1 text => $CFG->{profile}{default}{password},
1513     hidden => 1,
1514     tooltip => "The password for your character.",
1515     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1516     );
1517    
1518 root 1.18 $table->add_at (1, 11, $LOGIN_BUTTON = new DC::UI::Button
1519 root 1.1 expand => 1,
1520     text => "Login / Register",
1521     tooltip => "This button will either login to the account configured above or register a new account.",
1522     on_activate => sub {
1523     $CONN ? stop_game
1524     : start_game;
1525     1
1526     },
1527     );
1528    
1529 root 1.18 $vbox->add (new DC::UI::FancyFrame
1530 root 1.1 label => "Registering",
1531     min_h => 200,
1532 root 1.22 child => (new DC::UI::Label valign => 0, ellipsise => 0,
1533 root 1.1 markup =>
1534     "To register a new account, choose a username that hasn't been taken yet and "
1535     . "try to log-in. Follow the instructions in the Log tab in the message window.",
1536     ),
1537     );
1538    
1539     $vbox
1540     }
1541    
1542     sub server_setup {
1543 root 1.18 my $vbox = new DC::UI::VBox;
1544 root 1.1
1545 root 1.18 $vbox->add (new DC::UI::FancyFrame
1546 root 1.1 label => "Connection Settings",
1547 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1548 root 1.1 );
1549    
1550     my $row = 0;
1551    
1552 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Host:Port");
1553 root 1.1 {
1554 root 1.18 $table->add_at (1, $row, my $vbox = new DC::UI::VBox);
1555 root 1.1
1556     $vbox->add (
1557 root 1.18 $HOST_ENTRY = new DC::UI::Entry
1558 root 1.1 expand => 1,
1559     text => $CFG->{profile}{default}{host},
1560 root 1.28 tooltip => "The hostname or ip address of the Deliantra server to connect to (e.g. <b>gameserver.deliantra.net</b>)",
1561 root 1.1 on_changed => sub {
1562     my ($self, $value) = @_;
1563     $CFG->{profile}{default}{host} = $value;
1564     1
1565     }
1566     );
1567    
1568 root 1.5 if (0) { #d# disabled
1569 root 1.18 $vbox->add (new DC::UI::Button
1570 root 1.1 expand => 1,
1571     text => "Server List",
1572     other => $METASERVER,
1573 root 1.28 tooltip => "Show a list of available Deliantra servers",
1574 root 1.1 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1575     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1576     );
1577 root 1.5 }#d#
1578 root 1.1 }
1579    
1580 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Map Size");
1581 root 1.18 $table->add_at (1, $row, new DC::UI::Slider
1582 root 1.1 force_w => 100,
1583     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1584     tooltip => "This is the size of the portion of the map update the server sends you. "
1585     . "If you set this to a high value you will be able to see further, "
1586     . "but you also increase bandwidth requirements and latency. "
1587     . "This option is only used once at log-in.",
1588     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1589     );
1590    
1591 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Output-Rate");
1592 root 1.18 $table->add_at (1, $row, new DC::UI::Entry
1593 root 1.1 text => $CFG->{output_rate},
1594     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1595     . "when sending data. When 0 or unset, the server "
1596     . "default will be used, which is usually around 100kb/s. Most servers will "
1597     . "dynamically find an optimal rate, so adjust this only when necessary.",
1598     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1599     );
1600    
1601 root 1.18 $vbox->add (new DC::UI::FancyFrame
1602 root 1.1 label => "Server Info",
1603 root 1.18 child => ($SERVER_INFO = new DC::UI::Label ellipsise => 0),
1604 root 1.1 );
1605    
1606     $vbox
1607     }
1608    
1609     sub client_setup {
1610 root 1.18 my $table = new DC::UI::Table expand => 1, col_expand => [0, 1];
1611 root 1.1
1612     my $row = 0;
1613    
1614 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Tip of the day");
1615 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1616 root 1.1 state => $CFG->{show_tips},
1617     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1618     on_changed => sub {
1619     my ($self, $value) = @_;
1620     $CFG->{show_tips} = $value;
1621     0
1622     }
1623     );
1624    
1625 root 1.37 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Window Size");
1626 root 1.18 $table->add_at (1, $row++, my $saycmd = new DC::UI::Entry
1627 root 1.1 text => $CFG->{logview_max_par},
1628 root 1.37 tooltip => "This is maximum number of messages remembered in the <b>Message</b> window. If the server "
1629 root 1.1 . "sends more messages than this number, older messages get removed to save memory and "
1630     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1631     on_changed => sub {
1632     my ($self, $value) = @_;
1633 root 1.28 $MESSAGE_DIST->set_max_par ($CFG->{logview_max_par} = $value*1);
1634 root 1.1 0
1635     },
1636     );
1637    
1638     $table
1639     }
1640    
1641     sub autopickup_setup {
1642 root 1.18 my $r = new DC::UI::ScrolledWindow (
1643 root 1.1 expand => 1,
1644     scroll_y => 1
1645     );
1646 root 1.18 $r->add (my $table = new DC::UI::Table
1647 root 1.1 row_expand => [0],
1648     col_expand => [0, 1, 0, 1],
1649     );
1650    
1651     for (
1652     ["General", 0, 0,
1653     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1654     ["Inhibit autopickup" => PICKUP_INHIBIT],
1655     ["Stop before pickup" => PICKUP_STOP],
1656     ["Debug autopickup" => PICKUP_DEBUG],
1657     ],
1658     ["Weapons", 0, 6,
1659     ["All weapons" => PICKUP_ALLWEAPON],
1660     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1661     ["Bows" => PICKUP_BOW],
1662     ["Arrows" => PICKUP_ARROW],
1663     ],
1664     ["Armour", 0, 12,
1665     ["Helmets" => PICKUP_HELMET],
1666     ["Shields" => PICKUP_SHIELD],
1667     ["Body Armour" => PICKUP_ARMOUR],
1668     ["Boots" => PICKUP_BOOTS],
1669     ["Gloves" => PICKUP_GLOVES],
1670     ["Cloaks" => PICKUP_CLOAK],
1671     ],
1672    
1673     ["Readables", 2, 0,
1674     ["Spellbooks" => PICKUP_SPELLBOOK],
1675     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1676     ["Normal Books/Scrolls" => PICKUP_READABLES],
1677     ],
1678     ["Misc", 2, 5,
1679     ["Food" => PICKUP_FOOD],
1680     ["Drinks" => PICKUP_DRINK],
1681     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1682     ["Keys" => PICKUP_KEY],
1683     ["Magical Items" => PICKUP_MAGICAL],
1684     ["Potions" => PICKUP_POTION],
1685     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1686     ["Ignore cursed" => PICKUP_NOT_CURSED],
1687     ["Jewelery" => PICKUP_JEWELS],
1688     ["Flesh" => PICKUP_FLESH],
1689     ],
1690     ["Weight/Value ratio", 2, 17]
1691     )
1692     {
1693     my ($title, $x, $y, @bits) = @$_;
1694 root 1.18 $table->add_at ($x, $y, new DC::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1695 root 1.1
1696     for (@bits) {
1697     ++$y;
1698    
1699     my $mask = $_->[1];
1700 root 1.18 $table->add_at ($x , $y, new DC::UI::Label text => $_->[0], align => 1, expand => 1);
1701     $table->add_at ($x+1, $y, my $checkbox = new DC::UI::CheckBox
1702 root 1.1 state => $::CFG->{pickup} & $mask,
1703     on_changed => sub {
1704     my ($box, $value) = @_;
1705    
1706     if ($value) {
1707     $::CFG->{pickup} |= $mask;
1708     } else {
1709     $::CFG->{pickup} &= ~$mask;
1710     }
1711    
1712     $::CONN->send_command ("pickup $::CFG->{pickup}")
1713     if defined $::CONN;
1714    
1715     0
1716     });
1717    
1718     ${$_->[2]} = $checkbox if $_->[2];
1719     }
1720     }
1721    
1722 root 1.18 $table->add_at (2, 18, new DC::UI::ValSlider
1723 root 1.1 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1724     template => ">= 99",
1725     to_value => sub { ">= " . 5 * $_[0] },
1726     on_changed => sub {
1727     my ($slider, $value) = @_;
1728    
1729     $::CFG->{pickup} &= ~0xF;
1730     $::CFG->{pickup} |= int $value
1731     if $value;
1732     1;
1733     });
1734    
1735 root 1.18 $table->add_at (3, 18, new DC::UI::Button
1736 root 1.1 text => "set",
1737     on_activate => sub {
1738     $::CONN->send_command ("pickup $::CFG->{pickup}")
1739     if defined $::CONN;
1740     0
1741     });
1742    
1743     $r
1744     }
1745    
1746     my %SORT_ORDER = (
1747 elmex 1.41 type => sub {
1748     sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @_
1749     },
1750 root 1.1 mtime => sub {
1751     my $NOW = time;
1752     sort {
1753     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1754     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1755    
1756     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1757     or $btime <=> $atime
1758     or $a->{type} <=> $b->{type}
1759     } @_
1760     },
1761     weight => sub { sort {
1762     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1763     or $a->{type} <=> $b->{type}
1764     } @_ },
1765     );
1766    
1767     sub inventory_widget {
1768 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1769 root 1.1
1770 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1771 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1772 root 1.1
1773 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1774 root 1.1
1775     use sort 'stable';
1776    
1777 root 1.18 $hb1->add (new DC::UI::Selector
1778 root 1.1 value => $::CFG->{inv_sort},
1779     options => [
1780     [type => "Type/Name"],
1781     [mtime => "Recent/Normal/Locked"],
1782     [weight => "Weight/Type"],
1783     ],
1784     on_changed => sub {
1785     $::CFG->{inv_sort} = $_[1];
1786     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1787     },
1788     );
1789 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1790 root 1.1 #TODO# update to weigh/maxweight
1791 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1792 root 1.1
1793 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1794     $sw1->add ($INV = new DC::UI::Inventory);
1795 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1796    
1797 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1798 root 1.1
1799 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1800 root 1.1
1801 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1802     $sw2->add ($INVR = new DC::UI::Inventory);
1803 root 1.1
1804     # XXX: Call after $INVR = ... because set_opencont sets the items
1805 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
1806 root 1.1
1807     $hb
1808     }
1809    
1810     sub media_window {
1811 root 1.18 my $vb = new DC::UI::VBox;
1812 root 1.1
1813 root 1.18 $vb->add (new DC::UI::FancyFrame
1814 root 1.1 label => "Currently playing music",
1815 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1816     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
1817 root 1.1 );
1818    
1819 root 1.18 $vb->add (new DC::UI::FancyFrame
1820 root 1.1 label => "Other media used in this session",
1821     expand => 1,
1822 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
1823 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1824     );
1825    
1826     $vb
1827     }
1828    
1829     sub add_license {
1830     my ($meta) = @_;
1831    
1832     $meta = $meta->{data}
1833     or return;
1834    
1835     $meta->{license} || $meta->{author} || $meta->{source}
1836     or return;
1837    
1838     $LICENSE_WIDGET->add_paragraph ({
1839     fg => [1, 1, 1, 1],
1840     markup => "<small>"
1841 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
1842     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
1843     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
1844     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
1845 root 1.1 . "</small>",
1846     });
1847     $LICENSE_WIDGET->scroll_to_bottom;
1848     }
1849    
1850     sub toggle_player_page {
1851     my ($widget) = @_;
1852    
1853     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1854     $PL_WINDOW->hide;
1855     } else {
1856     $PL_NOTEBOOK->set_current_page ($widget);
1857     $PL_WINDOW->show;
1858     }
1859     }
1860    
1861     sub player_window {
1862 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
1863 root 1.1 x => "center",
1864     y => "center",
1865     force_w => $WIDTH * 9/10,
1866     force_h => $HEIGHT * 9/10,
1867     title => "Player",
1868     name => "playerbook",
1869     has_close_button => 1
1870     ;
1871    
1872     my $ntb =
1873     $PL_NOTEBOOK =
1874 root 1.18 new DC::UI::Notebook expand => 1;
1875 root 1.1
1876     $ntb->add_tab (
1877     "Statistics (F2)" => $STATS_PAGE = stats_window,
1878     "Shows statistics, where all your Stats and Resistances are shown."
1879     );
1880     $ntb->add_tab (
1881     "Skills (F3)" => $SKILL_PAGE = skill_window,
1882     "Shows all your Skills."
1883     );
1884    
1885 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1886     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
1887 root 1.1 $ntb->add_tab (
1888     "Spellbook (F4)" => $spellsw,
1889     "Displays all spells you have and lets you edit keyboard shortcuts for them."
1890     );
1891     $ntb->add_tab (
1892     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1893     "Toggles the inventory window, where you can manage your loot (or treasures :). "
1894     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1895     );
1896     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
1897     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1898    
1899     $ntb->add_tab (Media => media_window,
1900     "License, Author and Source info for media sent by the server.");
1901    
1902     $ntb->set_current_page ($INVENTORY_PAGE);
1903    
1904     $plwin->add ($ntb);
1905     $plwin
1906     }
1907    
1908     sub keyboard_setup {
1909 root 1.18 DC::Macro::keyboard_setup
1910 root 1.1 }
1911    
1912     sub help_window {
1913 root 1.18 my $win = new DC::UI::Toplevel
1914 root 1.1 x => 'center',
1915     y => 'center',
1916     z => 4,
1917     name => 'doc_browser',
1918     force_w => int $WIDTH * 7/8,
1919     force_h => int $HEIGHT * 7/8,
1920     title => "Help Browser",
1921     has_close_button => 1;
1922    
1923 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
1924 root 1.1
1925 root 1.18 $vbox->add (new DC::UI::FancyFrame
1926 root 1.1 label => "Navigation",
1927 root 1.18 child => (my $buttons = new DC::UI::HBox),
1928 root 1.1 );
1929 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
1930 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1931    
1932     my @history;
1933     my @future;
1934     my $curnode;
1935    
1936     my $load_node; $load_node = sub {
1937     my ($node, $para) = @_;
1938    
1939     $buttons->clear;
1940    
1941 root 1.18 $buttons->add (new DC::UI::Button
1942 root 1.1 text => "⇤",
1943     tooltip => "back to the starting page",
1944     on_activate => sub {
1945     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1946     unshift @future, @history;
1947     @history = ();
1948     $load_node->(@{shift @future});
1949     },
1950     );
1951    
1952     if (@history) {
1953 root 1.18 $buttons->add (new DC::UI::Button
1954 root 1.1 text => "⋘",
1955 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
1956 root 1.1 on_activate => sub {
1957     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1958     $load_node->(@{pop @history});
1959     },
1960     );
1961     }
1962    
1963     if (@future) {
1964 root 1.18 $buttons->add (new DC::UI::Button
1965 root 1.1 text => "⋙",
1966 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
1967 root 1.1 on_activate => sub {
1968     push @history, [$curnode, $viewer->current_paragraph];
1969     $load_node->(@{shift @future});
1970     },
1971     );
1972     }
1973    
1974 root 1.18 $buttons->add (new DC::UI::Label text => " ");
1975 root 1.1
1976 root 1.18 my @path = DC::Pod::full_path_of $node;
1977 root 1.1 pop @path; # drop current node
1978    
1979     for my $node (@path) {
1980 root 1.18 $buttons->add (new DC::UI::Button
1981 root 1.31 text => $node->[DC::Pod::N_KW][0],
1982 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
1983 root 1.1 on_activate => sub {
1984     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1985     $load_node->($node);
1986     },
1987     );
1988 root 1.18 $buttons->add (new DC::UI::Label text => "/");
1989 root 1.1 }
1990    
1991 root 1.31 $buttons->add (new DC::UI::Label text => $node->[DC::Pod::N_KW][0], padding_x => 4, padding_y => 4);
1992 root 1.1
1993     $curnode = $node;
1994    
1995     $viewer->clear;
1996 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
1997 root 1.1 $viewer->scroll_to ($para);
1998     };
1999    
2000 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
2001 root 1.1
2002 root 1.18 $DC::Pod::goto_document = sub {
2003 root 1.1 my (@path) = @_;
2004    
2005     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2006    
2007 root 1.18 $load_node->((DC::Pod::find @path)[0]);
2008 root 1.1 $win->show;
2009     };
2010    
2011     $win
2012     }
2013    
2014     sub open_string_query {
2015     my ($title, $cb, $txt, $tooltip) = @_;
2016 root 1.18 my $dialog = new DC::UI::Toplevel
2017 root 1.1 x => "center",
2018     y => "center",
2019     z => 50,
2020     force_w => $WIDTH * 4/5,
2021     title => $title;
2022    
2023     $dialog->add (
2024 root 1.18 my $e = new DC::UI::Entry
2025 root 1.1 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2026     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2027     tooltip => $tooltip
2028     );
2029    
2030     $e->grab_focus;
2031     $e->set_text ($txt) if $txt;
2032     $dialog->show;
2033     }
2034    
2035     sub open_quit_dialog {
2036     unless ($QUIT_DIALOG) {
2037 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2038 root 1.1 x => "center",
2039     y => "center",
2040     z => 50,
2041     title => "Really Quit?",
2042     on_key_down => sub {
2043     my ($dialog, $ev) = @_;
2044     $ev->{sym} == 27 and $dialog->hide;
2045     }
2046     ;
2047    
2048 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2049 root 1.1
2050 root 1.18 $vb->add (new DC::UI::Label
2051 root 1.1 text => "You should find a savebed and apply it first!",
2052     max_w => $WIDTH * 0.25,
2053     ellipsize => 0,
2054     );
2055 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2056     $hb->add (new DC::UI::Button
2057 root 1.1 text => "Ok",
2058     expand => 1,
2059     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2060     );
2061 root 1.18 $hb->add (new DC::UI::Button
2062 root 1.1 text => "Quit anyway",
2063     expand => 1,
2064 root 1.4 on_activate => sub { EV::unloop EV::UNLOOP_ALL },
2065 root 1.1 );
2066     }
2067    
2068     $QUIT_DIALOG->show;
2069     $QUIT_DIALOG->grab_focus;
2070     }
2071    
2072     sub show_tip_of_the_day {
2073     # find all tips
2074 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2075 root 1.1
2076 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2077 root 1.1 my ($todindex) = @_;
2078     $todindex = 0 if $todindex >= @tod;
2079 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2080 root 1.1
2081     # create dialog
2082     my $dialog;
2083    
2084     my $close = sub {
2085     $dialog->destroy;
2086     };
2087    
2088 root 1.18 $dialog = new DC::UI::Toplevel
2089 root 1.1 x => "center",
2090     y => "center",
2091     z => 3,
2092     name => 'tip_of_the_day',
2093     force_w => int $WIDTH * 4/9,
2094     force_h => int $WIDTH * 2/9,
2095     title => "Tip of the day #" . (1 + $todindex),
2096 root 1.18 child => my $vbox = new DC::UI::VBox,
2097 root 1.1 has_close_button => 1,
2098     on_delete => $close,
2099     ;
2100    
2101 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2102 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2103 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2104 root 1.1
2105 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2106 root 1.1
2107 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2108 root 1.1 text => "Close",
2109     tooltip => "Close the tip of the day window. To never see it again, disable the tip of the day in the <b>Server Setup</b>.",
2110     on_activate => $close,
2111     );
2112    
2113 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2114 root 1.1 text => "Next",
2115     tooltip => "Show the next <b>Tip of the day</b>.",
2116     on_activate => sub {
2117     $close->();
2118     &show_tip_of_the_day;
2119     },
2120     );
2121    
2122     $dialog->show;
2123     };
2124     }
2125    
2126     sub sdl_init {
2127 root 1.18 DC::SDL_Init
2128 root 1.1 and die "SDL::Init failed!\n";
2129     }
2130    
2131     sub video_init {
2132     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
2133    
2134     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2135    
2136     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2137     $FULLSCREEN = $CFG->{fullscreen};
2138     $FAST = $CFG->{fast};
2139    
2140 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2141     or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2142 root 1.1
2143     $SDL_ACTIVE = 1;
2144     $LAST_REFRESH = time - 0.01;
2145    
2146 root 1.18 DC::OpenGL::init;
2147     DC::Macro::init;
2148 root 1.1
2149     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2150    
2151 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2152 root 1.1
2153     #############################################################################
2154    
2155     if ($DEBUG_STATUS) {
2156 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2157 root 1.1 } else {
2158     # create/configure the widgets
2159    
2160 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2161 root 1.1 my (undef, $ev) = @_;
2162    
2163 root 1.18 if (my @macros = DC::Macro::find $ev) {
2164     DC::Macro::execute $_ for @macros;
2165 root 1.1
2166     return 1;
2167     }
2168    
2169     0
2170     });
2171    
2172 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2173 root 1.1 padding => 0,
2174     z => 100,
2175     force_x => "max",
2176     force_y => 0;
2177     $DEBUG_STATUS->show;
2178    
2179 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2180 root 1.29
2181     $MODBOX = new DC::UI::Label
2182     can_events => 1,
2183     can_hover => 1,
2184     markup => "",
2185     align => 0,
2186     font => $FONT_FIXED,
2187 root 1.30 tooltip => "#modifier_box",
2188     tooltip_width => 0.67,
2189     ;
2190 root 1.29
2191     update_modbox;
2192 root 1.1
2193 root 1.18 (new DC::UI::Frame
2194 root 1.1 bg => [0, 0, 0, 0.4],
2195     force_x => 0,
2196     force_y => "max",
2197 root 1.29 child => (my $LR = new DC::UI::VBox),
2198 root 1.1 )->show;
2199    
2200 root 1.29 $LR->add ($STATUSBOX);
2201     $LR->add ($MODBOX);
2202     $LR->add (new DC::UI::Label
2203     align => 0,
2204     markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode",
2205     fontsize => 0.5,
2206     fg => [1, 1, 0, 0.7],
2207     );
2208    
2209 root 1.18 DC::UI::Toplevel->new (
2210 root 1.2 title => "Minimap",
2211 root 1.1 name => "mapmap",
2212     x => 0,
2213     y => $FONTSIZE + 8,
2214     border_bg => [1, 1, 1, 192/255],
2215     bg => [1, 1, 1, 0],
2216 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2217 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2218     ),
2219     )->show;
2220    
2221 root 1.18 $MAPWIDGET = new DC::MapWidget;
2222 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2223     my ($mapwidget, $preset) = @_;
2224    
2225 elmex 1.23 $MESSAGE_DIST->activate_console ($preset)
2226     if $MESSAGE_DIST;
2227 root 1.1 });
2228     $MAPWIDGET->show;
2229     $MAPWIDGET->grab_focus;
2230    
2231 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2232 root 1.1 command => { },
2233     tooltip => "#completer_help",
2234     ;
2235    
2236 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2237 root 1.1 title => "Setup",
2238     name => "setup_dialog",
2239     x => 'center',
2240     y => 'center',
2241     z => 2,
2242     force_w => $::WIDTH * 0.6,
2243     force_h => $::HEIGHT * 0.6,
2244     has_close_button => 1,
2245     ;
2246    
2247     $METASERVER = metaserver_dialog;
2248 root 1.39 # the name is changed to not conflict with the older name as users could have hidden it
2249 root 1.40 $MESSAGE_WINDOW = new DC::UI::Dockbar
2250     name => "message_window2",
2251     title => 'Messages',
2252     force_w => $::WIDTH * 0.6,
2253     force_h => $::HEIGHT * 0.25,
2254     ;
2255    
2256 elmex 1.23 $MESSAGE_DIST = new DC::MessageDistributor dockbar => $MESSAGE_WINDOW;
2257 root 1.1
2258 root 1.38 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1,
2259 root 1.18 filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2260 root 1.1
2261     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2262     "Configure the server to play on, your username and password.");
2263     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2264     "Configure other server related options.");
2265     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2266     "Configure various client-specific settings.");
2267     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2268     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2269     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2270     "Configure the use of audio, sound effects and background music.");
2271     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2272     "Lets you define, edit and delete key bindings."
2273     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2274     . "with nothing set and the recording started. After doing the actions you "
2275     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2276     . "After pressing the combo the binding will be saved automatically and the "
2277     . "binding editor closes");
2278     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2279     "Some debuggin' options. Do not ask.");
2280    
2281 root 1.18 $BUTTONBAR = new DC::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2282 root 1.1
2283 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2284 root 1.1 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2285    
2286 root 1.37 # $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2287     # tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2288 root 1.1
2289     make_gauge_window->show; # XXX: this has to be set before make_stats_window as make_stats_window calls update_stats_window which updated the gauges also X-D
2290    
2291 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Playerbook", other => player_window,
2292 root 1.1 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2293    
2294 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2295 root 1.1 text => "Save Config",
2296     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2297     on_activate => sub {
2298 root 1.18 $::CFG->{layout} = DC::UI::get_layout;
2299 root 1.35 DC::write_cfg;
2300 root 1.1 status "Configuration Saved";
2301     0
2302     },
2303     );
2304    
2305 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2306 root 1.1 tooltip => "View Documentation");
2307    
2308 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2309 root 1.1 text => "Quit",
2310     tooltip => "Terminates the program",
2311     on_activate => sub {
2312     if ($CONN) {
2313     open_quit_dialog;
2314     } else {
2315 root 1.4 EV::unloop EV::UNLOOP_ALL;
2316 root 1.1 }
2317     0
2318     },
2319     );
2320    
2321     $BUTTONBAR->show;
2322     $SETUP_DIALOG->show;
2323     $MESSAGE_WINDOW->show;
2324     }
2325    
2326     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2327     }
2328    
2329     sub video_shutdown {
2330 root 1.18 DC::OpenGL::shutdown;
2331 root 1.1
2332     undef $SDL_ACTIVE;
2333     }
2334    
2335     my %animate_object;
2336     my $animate_timer;
2337    
2338     my $fps = 9;
2339    
2340     sub force_refresh {
2341     if ($ENV{CFPLUS_DEBUG} & 4) {
2342     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2343     debug sprintf "%3.2f", $fps;
2344     }
2345    
2346 root 1.16 undef $WANT_REFRESH;
2347     $_[0]->stop;
2348 root 1.12
2349 root 1.18 $DC::UI::ROOT->draw;
2350     DC::SDL_GL_SwapBuffers;
2351 root 1.1 $LAST_REFRESH = $NOW;
2352     }
2353    
2354 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2355 root 1.1
2356 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2357     $NOW = EV::now;
2358 root 1.1
2359     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2360 root 1.18 for DC::poll_events;
2361 root 1.1
2362     if (%animate_object) {
2363     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2364 root 1.16 $WANT_REFRESH = 1;
2365 root 1.1 }
2366 root 1.16
2367     $want_refresh->start
2368     if $WANT_REFRESH;
2369 root 1.4 };
2370 root 1.1
2371     sub animation_start {
2372     my ($widget) = @_;
2373     $animate_object{$widget} = $widget;
2374     }
2375    
2376     sub animation_stop {
2377     my ($widget) = @_;
2378     delete $animate_object{$widget};
2379     }
2380    
2381     %SDL_CB = (
2382 root 1.18 DC::SDL_QUIT => sub {
2383 root 1.4 EV::unloop EV::UNLOOP_ALL;
2384 root 1.1 },
2385 root 1.18 DC::SDL_VIDEORESIZE => sub {
2386 root 1.1 },
2387 root 1.18 DC::SDL_VIDEOEXPOSE => sub {
2388     DC::UI::full_refresh;
2389 root 1.1 },
2390 root 1.18 DC::SDL_ACTIVEEVENT => sub {
2391 root 1.12 # not useful, as APPACTIVE includes only iconified state, not unmapped
2392 root 1.18 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2393     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2394     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2395     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2396     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2397 root 1.1 },
2398 root 1.18 DC::SDL_KEYDOWN => sub {
2399     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2400 root 1.1 # alt-enter
2401     $FULLSCREEN_ENABLE->toggle;
2402     video_shutdown;
2403     video_init;
2404     } else {
2405 root 1.29 &DC::UI::feed_sdl_key_down_event;
2406 root 1.1 }
2407 root 1.29 update_modbox;
2408     },
2409     DC::SDL_KEYUP => sub {
2410     &DC::UI::feed_sdl_key_up_event;
2411     update_modbox;
2412 root 1.1 },
2413 root 1.18 DC::SDL_MOUSEMOTION => \&DC::UI::feed_sdl_motion_event,
2414     DC::SDL_MOUSEBUTTONDOWN => \&DC::UI::feed_sdl_button_down_event,
2415     DC::SDL_MOUSEBUTTONUP => \&DC::UI::feed_sdl_button_up_event,
2416     DC::SDL_USEREVENT => sub {
2417 root 1.1 if ($_[0]{code} == 1) {
2418     audio_channel_finished $_[0]{data1};
2419     } elsif ($_[0]{code} == 0) {
2420     audio_music_finished;
2421     }
2422     },
2423     );
2424    
2425     #############################################################################
2426    
2427 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2428     EV::unloop;
2429     #d# TODO calling exit here hangs the process in some futex
2430     };
2431 root 1.1
2432     {
2433 root 1.34 DC::Pod::load_docwiki DC::find_rcfile "docwiki.pst";
2434    
2435 root 1.15 if (-e "$Deliantra::VARDIR/client.cf") {
2436 root 1.18 DC::read_cfg "$Deliantra::VARDIR/client.cf";
2437 root 1.15 } else {
2438     #TODO: compatibility cruft
2439 root 1.18 DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2440 root 1.27 print STDERR "INFO: used old configuration file\n";
2441 root 1.15 }
2442    
2443 root 1.18 DC::DB::Server::run;
2444 root 1.35
2445     if ($CFG->{db_schema} < 1) {
2446     warn "INFO: upgrading database schema from 0 to 1, mapcache and tilecache will be lost\n";
2447     DC::DB::nuke_db;
2448     $CFG->{db_schema} = 1;
2449     DC::write_cfg;
2450     }
2451    
2452 root 1.33 DC::DB::open_db;
2453 root 1.1
2454 root 1.18 DC::UI::set_layout ($::CFG->{layout});
2455 root 1.1
2456     my %DEF_CFG = (
2457     sdl_mode => 0,
2458 root 1.3 fullscreen => 1,
2459 root 1.1 fast => 0,
2460     force_opengl11 => undef,
2461     texture_compression => 1,
2462     map_scale => 1,
2463     fow_enable => 1,
2464     fow_intensity => 0,
2465     map_smoothing => 1,
2466     gui_fontsize => 1,
2467     log_fontsize => 0.7,
2468     gauge_fontsize => 1,
2469     gauge_size => 0.35,
2470     stat_fontsize => 0.7,
2471     mapsize => 100,
2472     audio_enable => 1,
2473     audio_hw_channels => 0,
2474     audio_hw_frequency => 0,
2475     audio_hw_chunksize => 0,
2476     audio_mix_channels => 8,
2477     effects_enable => 1,
2478     effects_volume => 1,
2479     bgm_enable => 1,
2480     bgm_volume => 0.5,
2481     output_rate => "",
2482     pickup => 0,
2483     inv_sort => "mtime",
2484     default => "profile", # default profile
2485     show_tips => 1,
2486     logview_max_par => 1000,
2487     );
2488    
2489     while (my ($k, $v) = each %DEF_CFG) {
2490     $CFG->{$k} = $v unless exists $CFG->{$k};
2491     }
2492    
2493     $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2494     $PROFILE = $CFG->{profile}{default};
2495    
2496     # convert old bindings (only default profile matters)
2497     if (my $bindings = delete $PROFILE->{bindings}) {
2498     while (my ($mod, $syms) = each %$bindings) {
2499     while (my ($sym, $cmds) = each %$syms) {
2500     push @{ $PROFILE->{macro} }, {
2501     accelkey => [$mod*1, $sym*1],
2502     action => $cmds,
2503     };
2504     }
2505     }
2506     }
2507    
2508     sdl_init;
2509    
2510 root 1.18 @SDL_MODES = DC::SDL_ListModes 8, 8;
2511     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2512     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2513 root 1.1
2514     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2515    
2516     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2517    
2518     {
2519 root 1.18 my @fonts = map DC::find_rcfile "fonts/$_", qw(
2520 root 1.1 DejaVuSans.ttf
2521     DejaVuSansMono.ttf
2522     DejaVuSans-Bold.ttf
2523     DejaVuSansMono-Bold.ttf
2524     DejaVuSans-Oblique.ttf
2525     DejaVuSansMono-Oblique.ttf
2526     DejaVuSans-BoldOblique.ttf
2527     DejaVuSansMono-BoldOblique.ttf
2528     );
2529    
2530 root 1.18 DC::add_font $_ for @fonts;
2531 root 1.1
2532 root 1.18 $FONT_PROP = new_from_file DC::Font $fonts[0];
2533     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2534 root 1.1
2535     $FONT_PROP->make_default;
2536 root 1.30
2537     DC::pango_init;
2538 root 1.1 }
2539    
2540     # compare mono (ft) vs. rgba (cairo)
2541     # ft - 1.8s, cairo 3s, even in alpha-only mode
2542     # for my $rgba (0..1) {
2543     # my $t1 = Time::HiRes::time;
2544     # for (1..1000) {
2545 root 1.18 # my $layout = DC::Layout->new ($rgba);
2546 root 1.1 # $layout->set_text ("hallo" x 100);
2547     # $layout->render;
2548     # }
2549     # my $t2 = Time::HiRes::time;
2550     # warn $t2-$t1;
2551     # }
2552    
2553     video_init;
2554     audio_init;
2555     }
2556    
2557     show_tip_of_the_day if $CFG->{show_tips};
2558    
2559 root 1.4 our $STARTUP_CANCEL = EV::idle sub {
2560     undef $::STARTUP_CANCEL;
2561 root 1.1 $startup_done->();
2562 root 1.4 };
2563 root 1.1
2564 root 1.36 delete $SIG{__DIE__};
2565 root 1.4 EV::loop;
2566 root 1.1
2567     #video_shutdown;
2568     #audio_shutdown;
2569 root 1.18 DC::OpenGL::quit;
2570     DC::SDL_Quit;
2571     DC::DB::Server::stop;
2572 root 1.1
2573     =head1 NAME
2574    
2575     deliantra - A Deliantra MORPG game client
2576    
2577     =head1 SYNOPSIS
2578    
2579     Just run it - no commandline arguments are supported.
2580    
2581     =head1 USAGE
2582    
2583     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2584     be used in fullscreen mode and interactively.
2585    
2586     =head1 DEBUGGING
2587    
2588    
2589     CFPLUS_DEBUG - environment variable
2590    
2591     1 draw borders around widgets
2592     2 add low-level widget info to tooltips
2593     4 show fps
2594     8 suppress tooltips
2595    
2596     =head1 AUTHOR
2597    
2598     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2599    
2600    
2601