ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.30
Committed: Mon Mar 24 01:15:22 2008 UTC (16 years, 2 months ago) by root
Branch: MAIN
CVS Tags: rel-0_9967
Changes since 1.29: +8 -4 lines
Log Message:
*** empty log message ***

File Contents

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