ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.34
Committed: Sun Mar 30 00:25:13 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
Changes since 1.33: +2 -0 lines
Log Message:
*** empty log message ***

File Contents

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