ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.52
Committed: Fri Jul 18 21:18:42 2008 UTC (15 years, 10 months ago) by root
Branch: MAIN
Changes since 1.51: +41 -14 lines
Log Message:
*** empty log message ***

File Contents

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