ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.106
Committed: Sat Apr 3 03:02:28 2010 UTC (14 years, 1 month ago) by root
Branch: MAIN
Changes since 1.105: +19 -14 lines
Log Message:
common-sense...

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