ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.29
Committed: Mon Mar 24 00:24:47 2008 UTC (16 years, 2 months ago) by root
Branch: MAIN
Changes since 1.28: +51 -4 lines
Log Message:
*** empty log message ***

File Contents

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