ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.65
Committed: Sat Aug 30 06:49:05 2008 UTC (15 years, 8 months ago) by root
Branch: MAIN
Changes since 1.64: +1 -2 lines
Log Message:
*** empty log message ***

File Contents

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