ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.105
Committed: Sat Apr 3 02:58:25 2010 UTC (14 years, 1 month ago) by root
Branch: MAIN
Changes since 1.104: +1 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.7 if ($ENV{DELIANTRA_CORO_DEBUG}) {
4     eval '
5     use Coro;
6     use Coro::EV;
7     use Coro::Debug;
8     our $debug = new_unix_server Coro::Debug "/tmp/dc";
9     ';
10     }
11    
12 root 1.1 # do splash-screen thingy on win32
13     my $startup_done = sub { };
14     BEGIN {
15     if (%PAR::LibCache && $^O eq "MSWin32") {
16     while (my ($filename, $zip) = each %PAR::LibCache) {
17     $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp");
18     }
19    
20     require Win32::GUI::SplashScreen;
21    
22 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     sort { $a->{type} <=> $b->{type} or $a->{name} cmp $b->{name} } @_
1947     },
1948 root 1.1 mtime => sub {
1949     my $NOW = time;
1950     sort {
1951     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1952     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1953    
1954     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1955     or $btime <=> $atime
1956     or $a->{type} <=> $b->{type}
1957     } @_
1958     },
1959     weight => sub { sort {
1960     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1961     or $a->{type} <=> $b->{type}
1962     } @_ },
1963     );
1964    
1965     sub inventory_widget {
1966 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1967 root 1.1
1968 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1969 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1970 root 1.1
1971 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1972 root 1.1
1973     use sort 'stable';
1974    
1975 root 1.18 $hb1->add (new DC::UI::Selector
1976 root 1.1 value => $::CFG->{inv_sort},
1977     options => [
1978     [type => "Type/Name"],
1979     [mtime => "Recent/Normal/Locked"],
1980     [weight => "Weight/Type"],
1981     ],
1982     on_changed => sub {
1983     $::CFG->{inv_sort} = $_[1];
1984     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1985     },
1986     );
1987 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1988 root 1.58 #TODO# update to weight/maxweight
1989 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1990 root 1.1
1991 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1992     $sw1->add ($INV = new DC::UI::Inventory);
1993 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1994    
1995 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1996 root 1.1
1997 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1998 root 1.1
1999 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2000     $sw2->add ($INVR = new DC::UI::Inventory);
2001 root 1.1
2002     # XXX: Call after $INVR = ... because set_opencont sets the items
2003 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
2004 root 1.1
2005     $hb
2006     }
2007    
2008     sub media_window {
2009 root 1.18 my $vb = new DC::UI::VBox;
2010 root 1.1
2011 root 1.18 $vb->add (new DC::UI::FancyFrame
2012 root 1.1 label => "Currently playing music",
2013 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
2014     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
2015 root 1.1 );
2016    
2017 root 1.18 $vb->add (new DC::UI::FancyFrame
2018 root 1.1 label => "Other media used in this session",
2019     expand => 1,
2020 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
2021 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
2022     );
2023    
2024     $vb
2025     }
2026    
2027     sub add_license {
2028     my ($meta) = @_;
2029    
2030     $meta = $meta->{data}
2031     or return;
2032    
2033     $meta->{license} || $meta->{author} || $meta->{source}
2034     or return;
2035    
2036     $LICENSE_WIDGET->add_paragraph ({
2037     fg => [1, 1, 1, 1],
2038     markup => "<small>"
2039 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
2040     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
2041     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
2042     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
2043 root 1.1 . "</small>",
2044     });
2045     $LICENSE_WIDGET->scroll_to_bottom;
2046     }
2047    
2048     sub toggle_player_page {
2049     my ($widget) = @_;
2050    
2051     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
2052     $PL_WINDOW->hide;
2053     } else {
2054     $PL_NOTEBOOK->set_current_page ($widget);
2055     $PL_WINDOW->show;
2056     }
2057     }
2058    
2059 root 1.85 sub make_playerbook {
2060 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
2061 root 1.1 x => "center",
2062     y => "center",
2063     force_w => $WIDTH * 9/10,
2064     force_h => $HEIGHT * 9/10,
2065     title => "Player",
2066     name => "playerbook",
2067     has_close_button => 1
2068     ;
2069    
2070     my $ntb =
2071     $PL_NOTEBOOK =
2072 root 1.18 new DC::UI::Notebook expand => 1;
2073 root 1.1
2074     $ntb->add_tab (
2075     "Statistics (F2)" => $STATS_PAGE = stats_window,
2076     "Shows statistics, where all your Stats and Resistances are shown."
2077     );
2078     $ntb->add_tab (
2079     "Skills (F3)" => $SKILL_PAGE = skill_window,
2080     "Shows all your Skills."
2081     );
2082    
2083 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
2084     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
2085 root 1.1 $ntb->add_tab (
2086     "Spellbook (F4)" => $spellsw,
2087     "Displays all spells you have and lets you edit keyboard shortcuts for them."
2088     );
2089     $ntb->add_tab (
2090     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
2091     "Toggles the inventory window, where you can manage your loot (or treasures :). "
2092     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
2093     );
2094     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
2095     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
2096    
2097     $ntb->add_tab (Media => media_window,
2098     "License, Author and Source info for media sent by the server.");
2099    
2100     $ntb->set_current_page ($INVENTORY_PAGE);
2101    
2102     $plwin->add ($ntb);
2103     }
2104    
2105     sub keyboard_setup {
2106 root 1.18 DC::Macro::keyboard_setup
2107 root 1.1 }
2108    
2109 root 1.86 sub make_help_window {
2110 root 1.18 my $win = new DC::UI::Toplevel
2111 root 1.1 x => 'center',
2112     y => 'center',
2113     z => 4,
2114     name => 'doc_browser',
2115     force_w => int $WIDTH * 7/8,
2116     force_h => int $HEIGHT * 7/8,
2117     title => "Help Browser",
2118     has_close_button => 1;
2119    
2120 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
2121 root 1.1
2122 root 1.18 $vbox->add (new DC::UI::FancyFrame
2123 root 1.1 label => "Navigation",
2124 root 1.18 child => (my $buttons = new DC::UI::HBox),
2125 root 1.1 );
2126 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2127 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2128    
2129     my @history;
2130     my @future;
2131     my $curnode;
2132    
2133     my $load_node; $load_node = sub {
2134     my ($node, $para) = @_;
2135    
2136     $buttons->clear;
2137    
2138 root 1.18 $buttons->add (new DC::UI::Button
2139 root 1.1 text => "⇤",
2140     tooltip => "back to the starting page",
2141     on_activate => sub {
2142     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2143     unshift @future, @history;
2144     @history = ();
2145     $load_node->(@{shift @future});
2146     },
2147     );
2148    
2149     if (@history) {
2150 root 1.18 $buttons->add (new DC::UI::Button
2151 root 1.1 text => "⋘",
2152 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
2153 root 1.1 on_activate => sub {
2154     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
2155     $load_node->(@{pop @history});
2156     },
2157     );
2158     }
2159    
2160     if (@future) {
2161 root 1.18 $buttons->add (new DC::UI::Button
2162 root 1.1 text => "⋙",
2163 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
2164 root 1.1 on_activate => sub {
2165     push @history, [$curnode, $viewer->current_paragraph];
2166     $load_node->(@{shift @future});
2167     },
2168     );
2169     }
2170    
2171 root 1.18 $buttons->add (new DC::UI::Label text => " ");
2172 root 1.1
2173 root 1.18 my @path = DC::Pod::full_path_of $node;
2174 root 1.1 pop @path; # drop current node
2175    
2176     for my $node (@path) {
2177 root 1.18 $buttons->add (new DC::UI::Button
2178 root 1.31 text => $node->[DC::Pod::N_KW][0],
2179 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
2180 root 1.1 on_activate => sub {
2181     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2182     $load_node->($node);
2183     },
2184     );
2185 root 1.18 $buttons->add (new DC::UI::Label text => "/");
2186 root 1.1 }
2187    
2188 root 1.31 $buttons->add (new DC::UI::Label text => $node->[DC::Pod::N_KW][0], padding_x => 4, padding_y => 4);
2189 root 1.1
2190     $curnode = $node;
2191    
2192     $viewer->clear;
2193 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
2194 root 1.1 $viewer->scroll_to ($para);
2195     };
2196    
2197 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
2198 root 1.1
2199 root 1.18 $DC::Pod::goto_document = sub {
2200 root 1.1 my (@path) = @_;
2201    
2202     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
2203    
2204 root 1.18 $load_node->((DC::Pod::find @path)[0]);
2205 root 1.1 $win->show;
2206     };
2207    
2208 root 1.86 $HELP_WINDOW = $win;
2209 root 1.1 }
2210    
2211     sub open_quit_dialog {
2212     unless ($QUIT_DIALOG) {
2213 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2214 root 1.1 x => "center",
2215     y => "center",
2216     z => 50,
2217     title => "Really Quit?",
2218     on_key_down => sub {
2219     my ($dialog, $ev) = @_;
2220     $ev->{sym} == 27 and $dialog->hide;
2221     }
2222     ;
2223    
2224 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2225 root 1.1
2226 root 1.18 $vb->add (new DC::UI::Label
2227 root 1.1 text => "You should find a savebed and apply it first!",
2228     max_w => $WIDTH * 0.25,
2229     ellipsize => 0,
2230     );
2231 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2232     $hb->add (new DC::UI::Button
2233 root 1.1 text => "Ok",
2234     expand => 1,
2235     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2236     );
2237 root 1.18 $hb->add (new DC::UI::Button
2238 root 1.1 text => "Quit anyway",
2239     expand => 1,
2240 root 1.66 on_activate => sub {
2241     crash "Quit anyway";
2242     EV::unloop EV::UNLOOP_ALL;
2243     },
2244 root 1.1 );
2245     }
2246    
2247     $QUIT_DIALOG->show;
2248     $QUIT_DIALOG->grab_focus;
2249     }
2250    
2251 root 1.86 sub make_menubar {
2252 root 1.85 $MENUFRAME = new DC::UI::Toplevel
2253     border => 0,
2254     force_x => 0,
2255     force_y => 0,
2256     force_w => $::WIDTH,
2257     child => ($MENUBAR = new DC::UI::HBox),
2258     ;
2259    
2260     $MENUBAR->add ($BUTTONBAR = new DC::UI::Buttonbar);
2261    
2262     # 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
2263     make_gauge_window->show;
2264    
2265     # $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2266     # tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2267    
2268     make_playerbook;
2269    
2270     $MENUPOPUP = DC::UI::Menu->new (items => [
2271 root 1.86 ["Setup…\tF9" , sub { $SETUP_DIALOG->toggle_visibility }],
2272     ["Playerbook…\tTab" , sub { $PL_WINDOW ->toggle_visibility }],
2273     ["…Statistics\tF2" , sub { toggle_player_page ($::STATS_PAGE) }],
2274     ["…Skills\tF3" , sub { toggle_player_page ($::SKILL_PAGE) }],
2275     ["…Spells\tF4" , sub { toggle_player_page ($::SPELL_PAGE) }],
2276     ["…Inventory\tF5" , sub { toggle_player_page ($::INVENTORY_PAGE) }],
2277     ["Help Browser…\tF1" , sub { $HELP_WINDOW ->toggle_visibility }],
2278     ["Quit…" , sub {
2279 root 1.85 if ($CONN) {
2280     open_quit_dialog;
2281     } else {
2282     EV::unloop EV::UNLOOP_ALL;
2283     }
2284     }],
2285     ]);
2286    
2287     $BUTTONBAR->add (new DC::UI::Button text => "Menu…",
2288     tooltip => "Shows the main menu",
2289 root 1.86 on_button_down => sub {
2290     my ($self, $ev) = @_;
2291     local $ev->{x} = 0;
2292     local $ev->{y} = 0;
2293     $MENUPOPUP->popup ($ev);
2294     },
2295 root 1.85 );
2296    
2297 root 1.86 $MENUBAR->add ($GAUGES->{exp} = new DC::UI::ExperienceProgress
2298 root 1.85 padding_x => 6,
2299     padding_y => 3,
2300 root 1.90 tooltip => "This progress bar shows your overall experience and your progress towards the next character level.",
2301 root 1.87 template => " Exp: 888,888,888,888 (lvl 188) ",
2302 root 1.85 );
2303 root 1.86
2304     $MENUBAR->add ($PICKUP_ENABLE = new DC::UI::CheckBox # checkbox bad, button better?
2305     tooltip => "Automatic Pickup Enable - when this checkbox is enabled, then your character "
2306     . "will automatically pick up items as defined by your item pickup settings "
2307     . "in the playerbook. Often (e.g. in apartments) you want to temporarily "
2308 root 1.89 . "disable autopickup by disabling this checkbox.",
2309 root 1.86 state => $CFG->{pickup} & PICKUP_INHIBIT ? 0 : 1,
2310     on_changed => sub {
2311     my ($self, $value) = @_;
2312     $CFG->{pickup} &= ~PICKUP_INHIBIT;
2313     $CFG->{pickup} |= PICKUP_INHIBIT unless $_[1];
2314     $CONN->send_pickup ($CFG->{pickup})
2315     if $CONN;
2316     },
2317 root 1.85 );
2318 root 1.86
2319     $MENUBAR->add ($GAUGES->{skillexp} = new DC::UI::ExperienceProgress
2320 root 1.85 c_rescale => 1,
2321     padding_x => 6,
2322     padding_y => 3,
2323     force_w => $::WIDTH * 0.2,
2324     tooltip => "This progress bar shows the currently used skill and your progress towards the next skill level of that skill.",
2325 root 1.86 template => "two handed weapons 99%",
2326 root 1.85 );
2327 root 1.86
2328 root 1.85 $MENUBAR->add ($GAUGES->{range} = new DC::UI::Label
2329     expand => 1,
2330     align => 1, can_hover => 1, can_events => 1,
2331     text => "Range and Combat Slots",
2332     tooltip => "#stat_ranged",
2333     );
2334    
2335     $MENUFRAME->show;
2336     }
2337    
2338     sub open_string_query {
2339     my ($title, $cb, $txt, $tooltip) = @_;
2340     my $dialog = new DC::UI::Toplevel
2341     x => "center",
2342     y => "center",
2343     z => 50,
2344     force_w => $WIDTH * 4/5,
2345     title => $title;
2346    
2347     $dialog->add (
2348     my $e = new DC::UI::Entry
2349     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
2350     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
2351     tooltip => $tooltip
2352     );
2353    
2354     $e->grab_focus;
2355     $e->set_text ($txt) if $txt;
2356     $dialog->show;
2357     }
2358    
2359 root 1.1 sub show_tip_of_the_day {
2360     # find all tips
2361 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2362 root 1.1
2363 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2364 root 1.1 my ($todindex) = @_;
2365     $todindex = 0 if $todindex >= @tod;
2366 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2367 root 1.1
2368     # create dialog
2369     my $dialog;
2370    
2371     my $close = sub {
2372     $dialog->destroy;
2373     };
2374    
2375 root 1.18 $dialog = new DC::UI::Toplevel
2376 root 1.1 x => "center",
2377     y => "center",
2378     z => 3,
2379     name => 'tip_of_the_day',
2380     force_w => int $WIDTH * 4/9,
2381     force_h => int $WIDTH * 2/9,
2382     title => "Tip of the day #" . (1 + $todindex),
2383 root 1.18 child => my $vbox = new DC::UI::VBox,
2384 root 1.1 has_close_button => 1,
2385     on_delete => $close,
2386     ;
2387    
2388 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2389 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2390 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2391 root 1.1
2392 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2393 root 1.1
2394 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2395 root 1.1 text => "Close",
2396     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>.",
2397     on_activate => $close,
2398     );
2399    
2400 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2401 root 1.1 text => "Next",
2402     tooltip => "Show the next <b>Tip of the day</b>.",
2403     on_activate => sub {
2404     $close->();
2405     &show_tip_of_the_day;
2406     },
2407     );
2408    
2409     $dialog->show;
2410     };
2411     }
2412    
2413     sub sdl_init {
2414 root 1.52 DC::SDL_Init DC::SDL_INIT_AUDIO #| DC::SDL_NOPARACHUTE
2415 root 1.1 and die "SDL::Init failed!\n";
2416     }
2417    
2418     sub video_init {
2419 root 1.74 DC::set_theme $CFG->{uitheme};
2420 root 1.73
2421 root 1.52 DC::SDL_InitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2422     $SDL_REINIT = 0;
2423    
2424     @SDL_MODES = DC::SDL_ListModes 8, $CFG->{disable_alpha} ? 0 : 8;
2425     @SDL_MODES = DC::SDL_ListModes 8, 8 unless @SDL_MODES;
2426     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2427     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2428    
2429     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2430    
2431 root 1.61 if (!defined $CFG->{sdl_mode} or $CFG->{sdl_mode} > $#SDL_MODES) {
2432     $CFG->{sdl_mode} = 0; # lowest resolution by default
2433    
2434 root 1.72 # now choose biggest mode <= 1024x768
2435 root 1.61 for (0 .. $#SDL_MODES) {
2436     if ($SDL_MODES[$_][0] * $SDL_MODES[$_][1] <= 1024 * 768) {
2437     $CFG->{sdl_mode} = $_;
2438     }
2439 root 1.52 }
2440     }
2441 root 1.1
2442     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2443    
2444     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2445     $FULLSCREEN = $CFG->{fullscreen};
2446     $FAST = $CFG->{fast};
2447    
2448 root 1.59 # due to mac os x braindamage, we simply retry with !fullscreen in case of an error
2449 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2450 root 1.59 or DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, !$FULLSCREEN
2451 root 1.18 or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2452 root 1.1
2453     $SDL_ACTIVE = 1;
2454     $LAST_REFRESH = time - 0.01;
2455    
2456 root 1.18 DC::OpenGL::init;
2457     DC::Macro::init;
2458 root 1.1
2459     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2460    
2461 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2462 root 1.1
2463     #############################################################################
2464    
2465     if ($DEBUG_STATUS) {
2466 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2467 root 1.1 } else {
2468     # create/configure the widgets
2469    
2470 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2471 root 1.1 my (undef, $ev) = @_;
2472    
2473 root 1.18 if (my @macros = DC::Macro::find $ev) {
2474     DC::Macro::execute $_ for @macros;
2475 root 1.1
2476     return 1;
2477     }
2478    
2479     0
2480     });
2481    
2482 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2483 root 1.1 padding => 0,
2484     z => 100,
2485     force_x => "max",
2486     force_y => 0;
2487     $DEBUG_STATUS->show;
2488    
2489 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2490 root 1.29
2491     $MODBOX = new DC::UI::Label
2492     can_events => 1,
2493     can_hover => 1,
2494     markup => "",
2495     align => 0,
2496     font => $FONT_FIXED,
2497 root 1.30 tooltip => "#modifier_box",
2498     tooltip_width => 0.67,
2499     ;
2500 root 1.29
2501     update_modbox;
2502 root 1.1
2503 root 1.18 (new DC::UI::Frame
2504 root 1.1 bg => [0, 0, 0, 0.4],
2505     force_x => 0,
2506     force_y => "max",
2507 root 1.85 child => (my $LL = new DC::UI::VBox),
2508 root 1.1 )->show;
2509    
2510 root 1.85 $LL->add ($STATUSBOX);
2511     $LL->add ($MODBOX);
2512     $LL->add (new DC::UI::Label
2513 root 1.29 align => 0,
2514     markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode",
2515     fontsize => 0.5,
2516     fg => [1, 1, 0, 0.7],
2517     );
2518    
2519 root 1.18 DC::UI::Toplevel->new (
2520 root 1.2 title => "Minimap",
2521 root 1.1 name => "mapmap",
2522     x => 0,
2523 root 1.91 y => $::FONTSIZE + 8,#d# hack to move messages window below the menubar
2524 root 1.1 border_bg => [1, 1, 1, 192/255],
2525     bg => [1, 1, 1, 0],
2526 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2527 root 1.75 tooltip => "<b>Minimap</b>. This will display an overview of the surrounding areas.",
2528 root 1.1 ),
2529     )->show;
2530    
2531 root 1.18 $MAPWIDGET = new DC::MapWidget;
2532 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2533     my ($mapwidget, $preset) = @_;
2534    
2535 elmex 1.23 $MESSAGE_DIST->activate_console ($preset)
2536     if $MESSAGE_DIST;
2537 root 1.1 });
2538     $MAPWIDGET->show;
2539     $MAPWIDGET->grab_focus;
2540    
2541 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2542 root 1.1 command => { },
2543     tooltip => "#completer_help",
2544     ;
2545    
2546 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2547 root 1.1 title => "Setup",
2548     name => "setup_dialog",
2549     x => 'center',
2550     y => 'center',
2551     z => 2,
2552     force_w => $::WIDTH * 0.6,
2553     force_h => $::HEIGHT * 0.6,
2554     has_close_button => 1,
2555     ;
2556    
2557     $METASERVER = metaserver_dialog;
2558 root 1.39 # the name is changed to not conflict with the older name as users could have hidden it
2559 root 1.40 $MESSAGE_WINDOW = new DC::UI::Dockbar
2560     name => "message_window2",
2561     title => 'Messages',
2562 root 1.91 y => $::FONTSIZE + 8,#d# hack to move messages window below the menubar
2563 root 1.40 force_w => $::WIDTH * 0.6,
2564     force_h => $::HEIGHT * 0.25,
2565     ;
2566    
2567 elmex 1.23 $MESSAGE_DIST = new DC::MessageDistributor dockbar => $MESSAGE_WINDOW;
2568 root 1.1
2569 root 1.38 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1,
2570 root 1.18 filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2571 root 1.1
2572     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2573     "Configure the server to play on, your username and password.");
2574     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2575     "Configure other server related options.");
2576     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2577     "Configure various client-specific settings.");
2578     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2579     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2580     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2581     "Configure the use of audio, sound effects and background music.");
2582     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2583     "Lets you define, edit and delete key bindings."
2584     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2585     . "with nothing set and the recording started. After doing the actions you "
2586     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2587     . "After pressing the combo the binding will be saved automatically and the "
2588     . "binding editor closes");
2589     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2590     "Some debuggin' options. Do not ask.");
2591    
2592 root 1.86 make_help_window;
2593     make_menubar;
2594 root 1.1
2595     $SETUP_DIALOG->show;
2596     $MESSAGE_WINDOW->show;
2597     }
2598    
2599 root 1.72 $MODE_SLIDER->set_range ([$CFG->{sdl_mode}, 0, scalar @SDL_MODES, 1, 1]);
2600 root 1.53 $MODE_SLIDER->emit (changed => $CFG->{sdl_mode});
2601    
2602     $CAVEAT_LABEL->set_text ("None :)");
2603 root 1.97 $CAVEAT_LABEL->set_text ("Apple/NVIDIA Texture bug (slow)")
2604     if $DC::OpenGL::APPLE_NVIDIA_BUG;
2605 root 1.55 $CAVEAT_LABEL->set_text ("Software Rendering (very slow)")
2606 root 1.53 unless DC::SDL_GL_GetAttribute DC::SDL_GL_ACCELERATED_VISUAL;
2607    
2608 root 1.1 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2609     }
2610    
2611     sub video_shutdown {
2612 root 1.18 DC::OpenGL::shutdown;
2613 root 1.52 DC::SDL_QuitSubSystem DC::SDL_INIT_VIDEO if $SDL_REINIT;
2614 root 1.1
2615     undef $SDL_ACTIVE;
2616     }
2617    
2618     my %animate_object;
2619     my $animate_timer;
2620    
2621     my $fps = 9;
2622    
2623     sub force_refresh {
2624     if ($ENV{CFPLUS_DEBUG} & 4) {
2625     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2626     debug sprintf "%3.2f", $fps;
2627     }
2628    
2629 root 1.16 undef $WANT_REFRESH;
2630     $_[0]->stop;
2631 root 1.12
2632 root 1.18 $DC::UI::ROOT->draw;
2633     DC::SDL_GL_SwapBuffers;
2634 root 1.1 $LAST_REFRESH = $NOW;
2635     }
2636    
2637 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2638 root 1.1
2639 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2640     $NOW = EV::now;
2641 root 1.1
2642 root 1.90 ($SDL_CB[$_->{type}] || sub { warn "unhandled event $_->{type}" })->($_)
2643 root 1.18 for DC::poll_events;
2644 root 1.1
2645     if (%animate_object) {
2646     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2647 root 1.16 $WANT_REFRESH = 1;
2648 root 1.1 }
2649 root 1.16
2650     $want_refresh->start
2651     if $WANT_REFRESH;
2652 root 1.4 };
2653 root 1.1
2654     sub animation_start {
2655     my ($widget) = @_;
2656     $animate_object{$widget} = $widget;
2657     }
2658    
2659     sub animation_stop {
2660     my ($widget) = @_;
2661     delete $animate_object{$widget};
2662     }
2663    
2664 root 1.90 $SDL_CB[DC::SDL_QUIT] = sub {
2665     crash "SDL_QUIT";
2666     EV::unloop EV::UNLOOP_ALL;
2667     };
2668     $SDL_CB[DC::SDL_VIDEORESIZE] = sub { };
2669     $SDL_CB[DC::SDL_VIDEOEXPOSE] = sub {
2670     DC::UI::full_refresh;
2671     };
2672     $SDL_CB[DC::SDL_ACTIVEEVENT] = sub {
2673     # not useful, as APPACTIVE includes only iconified state, not unmapped
2674     # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2675     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2676     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2677     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2678     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2679     };
2680     $SDL_CB[DC::SDL_KEYDOWN] = sub {
2681     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2682     # alt-enter
2683     video_shutdown;
2684     $FULLSCREEN_ENABLE->toggle;
2685     video_init;
2686     } else {
2687     &DC::UI::feed_sdl_key_down_event;
2688     }
2689     update_modbox;
2690     };
2691     $SDL_CB[DC::SDL_KEYUP] = sub {
2692     &DC::UI::feed_sdl_key_up_event;
2693     update_modbox;
2694     };
2695     $SDL_CB[DC::SDL_MOUSEMOTION] = \&DC::UI::feed_sdl_motion_event,
2696     $SDL_CB[DC::SDL_MOUSEBUTTONDOWN] = \&DC::UI::feed_sdl_button_down_event,
2697     $SDL_CB[DC::SDL_MOUSEBUTTONUP] = \&DC::UI::feed_sdl_button_up_event,
2698     $SDL_CB[DC::SDL_USEREVENT] = sub {
2699     if ($_[0]{code} == 1) {
2700     audio_channel_finished $_[0]{data1};
2701     } elsif ($_[0]{code} == 0) {
2702     audio_music_finished;
2703     }
2704     };
2705 root 1.1
2706     #############################################################################
2707    
2708 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2709     EV::unloop;
2710     #d# TODO calling exit here hangs the process in some futex
2711     };
2712 root 1.1
2713 root 1.83 # due to mac os x + sdl combined braindamage, we need this contortion
2714 root 1.59 sub main {
2715     {
2716     DC::Pod::load_docwiki DC::find_rcfile "docwiki.pst";
2717 root 1.34
2718 root 1.59 if (-e "$Deliantra::VARDIR/client.cf") {
2719     DC::read_cfg "$Deliantra::VARDIR/client.cf";
2720     } else {
2721     #TODO: compatibility cruft
2722     DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2723     print STDERR "INFO: used old configuration file\n";
2724     }
2725 root 1.15
2726 root 1.59 DC::DB::Server::run;
2727 root 1.35
2728 root 1.59 if ($CFG->{db_schema} < 1) {
2729     warn "INFO: upgrading database schema from 0 to 1, mapcache and tilecache will be lost\n";
2730     DC::DB::nuke_db;
2731     $CFG->{db_schema} = 1;
2732     DC::write_cfg;
2733     }
2734 root 1.35
2735 root 1.59 DC::DB::open_db;
2736 root 1.1
2737 root 1.59 DC::UI::set_layout ($::CFG->{layout});
2738 root 1.1
2739 root 1.59 my %DEF_CFG = (
2740 root 1.85 config_autosave => 1,
2741 root 1.61 sdl_mode => undef,
2742 root 1.59 fullscreen => 1,
2743     fast => 0,
2744     force_opengl11 => undef,
2745     disable_alpha => 0,
2746     smooth_movement => 1,
2747 root 1.98 smooth_transitions => 1,
2748 root 1.59 texture_compression => 1,
2749     map_scale => 1,
2750     fow_enable => 1,
2751     fow_intensity => 0,
2752 root 1.99 fow_texture => 0,
2753 root 1.59 map_smoothing => 1,
2754     gui_fontsize => 1,
2755     log_fontsize => 0.7,
2756     gauge_fontsize => 1,
2757     gauge_size => 0.35,
2758     stat_fontsize => 0.7,
2759     mapsize => 100,
2760     audio_enable => 1,
2761     audio_hw_channels => 0,
2762     audio_hw_frequency => 0,
2763     audio_hw_chunksize => 0,
2764     audio_mix_channels => 8,
2765     effects_enable => 1,
2766     effects_volume => 1,
2767     bgm_enable => 1,
2768     bgm_volume => 0.5,
2769     output_rate => "",
2770 root 1.86 pickup => PICKUP_SPELLBOOK | PICKUP_SKILLSCROLL | PICKUP_VALUABLES,
2771 root 1.59 inv_sort => "mtime",
2772     default => "profile", # default profile
2773     show_tips => 1,
2774     logview_max_par => 1000,
2775     shift_fire_stop => 0,
2776 root 1.74 uitheme => "wood",
2777 root 1.92 map_shift_x => -24, # arbitrary
2778     map_shift_y => +24, # arbitrary
2779 root 1.59 );
2780 elmex 1.102
2781 root 1.59 while (my ($k, $v) = each %DEF_CFG) {
2782     $CFG->{$k} = $v unless exists $CFG->{$k};
2783     }
2784 root 1.1
2785 elmex 1.103 my @args = @ARGV;
2786 root 1.1
2787 elmex 1.103 my $profile = 'default';
2788    
2789     for (my $i = 0; $i < @args; $i++) {
2790 root 1.104 if ($args[$i] =~ /^--?profile$/) {
2791 elmex 1.103 $profile = $args[$i + 1];
2792     splice @args, $i, 2, ();
2793     $i = 0;
2794 root 1.104 } elsif ($args[$i] =~ /^--?h/) {
2795     print STDERR "Usage: $0 [--profile name] [host [user [password]]]\n";
2796     exit 0;
2797 elmex 1.103 }
2798     }
2799    
2800     $CFG->{profile}{$profile} ||= {};
2801     $PROFILE = $CFG->{profile}{$profile};
2802     $PROFILE->{host} ||= "gameserver.deliantra.net";
2803    
2804     $PROFILE->{host} = $args[0] if @args > 0;
2805     $PROFILE->{user} = $args[1] if @args > 1;
2806     $PROFILE->{password} = $args[2] if @args > 2;
2807 elmex 1.102
2808 root 1.59 # convert old bindings (only default profile matters)
2809     if (my $bindings = delete $PROFILE->{bindings}) {
2810     while (my ($mod, $syms) = each %$bindings) {
2811     while (my ($sym, $cmds) = each %$syms) {
2812     push @{ $PROFILE->{macro} }, {
2813     accelkey => [$mod*1, $sym*1],
2814     action => $cmds,
2815     };
2816     }
2817 root 1.1 }
2818     }
2819    
2820 root 1.59 sdl_init;
2821 root 1.1
2822 root 1.94 $ENV{FONTCONFIG_FILE} = DC::find_rcfile "fonts/fonts.conf";
2823     $ENV{FONTCONFIG_DIR} = DC::find_rcfile "fonts";
2824    
2825 root 1.59 {
2826     my @fonts = map DC::find_rcfile "fonts/$_", qw(
2827     DejaVuSans.ttf
2828     DejaVuSansMono.ttf
2829     DejaVuSans-Bold.ttf
2830     DejaVuSansMono-Bold.ttf
2831     DejaVuSans-Oblique.ttf
2832     DejaVuSansMono-Oblique.ttf
2833     DejaVuSans-BoldOblique.ttf
2834     DejaVuSansMono-BoldOblique.ttf
2835 root 1.94 mona.ttf
2836 root 1.59 );
2837    
2838     DC::add_font $_ for @fonts;
2839 elmex 1.102
2840 root 1.59 $FONT_PROP = new_from_file DC::Font $fonts[0];
2841     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2842 root 1.1
2843 root 1.59 $FONT_PROP->make_default;
2844 root 1.30
2845 root 1.59 DC::pango_init;
2846     }
2847 root 1.1
2848     # compare mono (ft) vs. rgba (cairo)
2849     # ft - 1.8s, cairo 3s, even in alpha-only mode
2850     # for my $rgba (0..1) {
2851     # my $t1 = Time::HiRes::time;
2852     # for (1..1000) {
2853 root 1.18 # my $layout = DC::Layout->new ($rgba);
2854 root 1.1 # $layout->set_text ("hallo" x 100);
2855     # $layout->render;
2856     # }
2857     # my $t2 = Time::HiRes::time;
2858     # warn $t2-$t1;
2859     # }
2860    
2861 root 1.59 video_init;
2862     audio_init;
2863     }
2864 root 1.1
2865 root 1.59 show_tip_of_the_day if $CFG->{show_tips};
2866 root 1.1
2867 root 1.59 our $STARTUP_CANCEL = EV::idle sub {
2868     undef $::STARTUP_CANCEL;
2869     $startup_done->();
2870     };
2871 root 1.1
2872 root 1.59 delete $SIG{__DIE__};
2873     EV::loop;
2874 root 1.1
2875 root 1.85 DC::write_cfg if $CFG->{config_autosave};
2876    
2877     #video_shutdown;
2878     #audio_shutdown;
2879    
2880 root 1.59 DC::OpenGL::quit;
2881     DC::SDL_Quit;
2882     DC::DB::Server::stop;
2883     }
2884    
2885     DC::SDL_braino; # see sub above
2886 root 1.1
2887     =head1 NAME
2888    
2889     deliantra - A Deliantra MORPG game client
2890    
2891     =head1 SYNOPSIS
2892    
2893 root 1.104 deliantra [--profile name] [host [user [password]]]
2894     deliantra --help
2895 root 1.1
2896     =head1 USAGE
2897    
2898 root 1.104 The deliantra client utilises OpenGL for all UI elements and the game. It
2899     is supposed to be used in fullscreen mode and interactively.
2900 root 1.1
2901     =head1 DEBUGGING
2902    
2903     CFPLUS_DEBUG - environment variable
2904    
2905     1 draw borders around widgets
2906     2 add low-level widget info to tooltips
2907     4 show fps
2908     8 suppress tooltips
2909    
2910     =head1 AUTHOR
2911    
2912 root 1.57 Marc Lehmann <deliantra@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2913 root 1.1
2914    
2915