ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.51
Committed: Thu Jul 17 15:23:43 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.50: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

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