ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.20
Committed: Fri Dec 28 11:44:02 2007 UTC (16 years, 4 months ago) by root
Branch: MAIN
Changes since 1.19: +4 -4 lines
Log Message:
*** empty log message ***

File Contents

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