ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.22
Committed: Sat Dec 29 13:44:31 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
Changes since 1.21: +53 -56 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     expand => 1,
659     can_events => 1,
660     can_hover => 1,
661     tooltip => "#stat_$name",
662     );
663     }
664    
665 root 1.18 unshift @dialog, new DC::UI::Label
666 root 1.1 max_w => $::WIDTH * 0.4,
667     ellipsise => 0,
668 root 1.18 markup => (DC::Pod::section_label ui => "chargen_stats"),
669 root 1.1 ;
670     }
671    
672 root 1.18 push @dialog, my $entry = new DC::UI::Entry
673 root 1.1 on_changed => sub {
674     $conn->send ("reply $_[1]");
675     destroy_query_dialog $conn;
676     0
677     },
678     ;
679    
680     $entry->grab_focus;
681    
682     } else {
683     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
684    
685 root 1.18 push @dialog, my $entry = new DC::UI::Entry
686 root 1.1 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
687     on_activate => sub {
688     $conn->send ("reply $_[1]");
689     destroy_query_dialog $conn;
690     0
691     },
692     ;
693    
694     $entry->grab_focus;
695     }
696    
697     $vbox->add (@dialog);
698     $dialog->show;
699     }
700    
701     sub start_game {
702     status "logging in...";
703    
704     $LOGIN_BUTTON->set_text ("Logout");
705     $SETUP_DIALOG->hide;
706    
707     my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
708    
709     my ($host, $port) = split /:/, $PROFILE->{host};
710    
711 root 1.18 $MAP = new DC::Map;
712 root 1.1
713     $CONN = eval {
714 root 1.18 new DC::Protocol
715 root 1.1 host => $host,
716     port => $port || 13327,
717     user => $PROFILE->{user},
718     pass => $PROFILE->{password},
719     mapw => $mapsize,
720     maph => $mapsize,
721    
722 root 1.18 client => "cfplus $DC::VERSION $] $^O",
723 root 1.1
724     map_widget => $MAPWIDGET,
725     statusbox => $STATUSBOX,
726     map => $MAP,
727     mapmap => $MAPMAP,
728     query => \&server_query,
729    
730     setup_req => {
731     smoothing => $CFG->{map_smoothing}*1,
732     },
733     };
734    
735     if ($CONN) {
736 root 1.18 DC::lowdelay fileno $CONN->{fh};
737 root 1.1
738     status "login successful";
739     } else {
740     status "unable to connect";
741     stop_game();
742     }
743     }
744    
745     sub stop_game {
746     $LOGIN_BUTTON->set_text ("Login / Register");
747     $SETUP_NOTEBOOK->set_current_page ($SETUP_LOGIN);
748     $SETUP_DIALOG->show;
749     $PL_WINDOW->hide;
750     $SPELL_LIST->clear_spells;
751 root 1.18 $DC::UI::ROOT->emit (stop_game => ! ! $CONN);
752 root 1.1
753     &audio_music_set_ambient ([]);
754    
755     return unless $CONN;
756    
757     status "connection closed";
758    
759     destroy_query_dialog $CONN;
760     $CONN->destroy;
761     $CONN = 0; # false, does not autovivify
762    
763     undef $MAP;
764     }
765    
766     sub graphics_setup {
767 root 1.18 my $vbox = new DC::UI::VBox;
768 root 1.1
769 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]);
770 root 1.1
771     my $row = 0;
772    
773 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "OpenGL Info");
774     $table->add_at (1, $row++, new DC::UI::Label fontsize => 0.8, text => DC::OpenGL::gl_vendor . ", " . DC::OpenGL::gl_version,
775 root 1.1 can_events => 1,
776 root 1.18 tooltip => "<tt><span size='8192'>" . (DC::OpenGL::gl_extensions) . "</span></tt>");
777 root 1.1
778     my $vidmode_tooltip =
779     "<b>Video Mode.</b> The video mode to use for fullscreen (and the window size for windowed operation). "
780     . "The format is <i>width</i> x <i>height</i> \@ <i>depth-per-channel</i> + <i>alpha-channel</i>.";
781    
782 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Video Mode");
783 root 1.18 $table->add_at (1, $row++, my $hbox = new DC::UI::HBox);
784 root 1.1
785 root 1.18 $hbox->add (my $mode_slider = new DC::UI::Slider
786 root 1.1 force_w => $WIDTH * 0.1, expand => 1, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1],
787     tooltip => $vidmode_tooltip);
788 root 1.18 $hbox->add (my $mode_label = new DC::UI::Label
789 root 1.22 height => 0.8, template => "9999x9999@9+9",
790 root 1.1 can_events => 1, tooltip => $vidmode_tooltip);
791    
792     $mode_slider->connect (changed => sub {
793     my ($self, $value) = @_;
794    
795     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
796     $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
797     });
798     $mode_slider->emit (changed => $mode_slider->{range}[0]);
799    
800 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fullscreen");
801 root 1.18 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new DC::UI::CheckBox
802 root 1.1 state => $CFG->{fullscreen},
803     tooltip => "Bring the client into fullscreen mode.",
804     on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
805     );
806    
807 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Force OpenGL 1.1");
808 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
809 root 1.1 state => $CFG->{force_opengl11},
810     tooltip => "Limit CFPlus to use OpenGL 1.1 features only. This will normally result in "
811     . "higher memory usage and slower performance. It will, however, help tremendously on "
812     . "cards that claim to support a feature but fall back to software rendering. "
813     . "Nvidia Geforce FX cards are known to claim features the hardware doesn't support, "
814     . "but cards and drivers from other vendors (ATI) are often just as bad. <b>If you "
815     . "experience extremely low framerates and your card should do better, try this option.</b>",
816     on_changed => sub { my ($self, $value) = @_; $CFG->{force_opengl11} = $value; 0 }
817     );
818    
819 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Compress Textures");
820 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
821 root 1.1 state => $CFG->{texture_compression},
822     tooltip => "Use texture compression. Normally this will not reduce visual quality noticable but "
823     . "will save a lot of memory and increase performance. The compression algorithm "
824     . "can differ form card to card, so your mileage may vary. This setting is ignored in "
825     . "forced OpenGL 1.1 mode.",
826     on_changed => sub { my ($self, $value) = @_; $CFG->{texture_compression} = $value; 0 }
827     );
828    
829 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fast & Ugly");
830 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
831 root 1.1 state => $CFG->{fast},
832     tooltip => "Lower the visual quality considerably to speed up rendering.",
833     on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
834     );
835    
836 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "GUI Fontsize");
837 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
838 root 1.1 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
839     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
840     on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
841     );
842    
843 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
844 root 1.22 expand => 1, text => "Apply",
845 root 1.1 tooltip => "Apply the video settings above.",
846     on_activate => sub {
847     video_shutdown ();
848     video_init ();
849     0
850     }
851     );
852    
853 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Scale");
854 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
855 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
856     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
857     on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
858     );
859    
860 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Map Smoothing");
861 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
862 root 1.1 state => $CFG->{map_smoothing},
863     tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
864     . "This increases load on the graphics subsystem and works only with TRT servers. "
865     . "Changes take effect at next login only.",
866     on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
867     );
868    
869 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Fog of War");
870 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
871 root 1.1 state => $CFG->{fow_enable},
872     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
873     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
874     );
875    
876 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "FoW Intensity");
877 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
878 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
879     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
880     on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
881     );
882    
883 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Message Fontsize");
884 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
885 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
886     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant, "
887     . "but you still need to press apply to correctly re-layout the widget.",
888     on_changed => sub { $MESSAGE_WINDOW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
889     );
890    
891 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge fontsize");
892 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
893 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
894     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
895     on_changed => sub {
896     $CFG->{gauge_fontsize} = $_[1];
897     &set_gauge_window_fontsize;
898     0
899     }
900     );
901    
902 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Gauge size");
903 root 1.18 $table->add_at (1, $row++, new DC::UI::Slider
904 root 1.1 range => [$CFG->{gauge_size}, 0.2, 0.8],
905     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
906     on_changed => sub {
907     $CFG->{gauge_size} = $_[1];
908     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
909     0
910     }
911     );
912    
913     $vbox
914     }
915    
916     our $AUDIO_HW_CHUNKSIZE;
917     our $AUDIO_INFO;
918    
919     sub audio_tab_update {
920 root 1.18 my ($freq, $format, $chans) = DC::Mix_QuerySpec;
921 root 1.1
922     $AUDIO_HW_CHUNKSIZE->set_options ([
923     [0, "default", "Use System Default"],
924     map {
925     my $ms = sprintf "%dms", 1000 * $_ / ($CFG->{audio_hw_frequency} || 22050);
926     [$_, $ms, "$ms ($_ samples)"],
927     } 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
928     ]);
929    
930     my $text = !$freq
931     ? "audio is off"
932     : "audio is enabled\n"
933     . "frequency (Hz): $freq\n"
934     . "channels: $chans";
935    
936     $AUDIO_INFO->set_text ($text);
937     }
938    
939     sub audio_setup {
940 root 1.18 my $vbox = new DC::UI::VBox;
941 root 1.1
942 root 1.18 $vbox->add (my $table = new DC::UI::Table expand => 1, col_expand => [0, 0, 1]);
943 root 1.1
944     my $row = 0;
945    
946 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Audio Enable");
947 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
948 root 1.1 state => $CFG->{audio_enable},
949     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.",
950     on_changed => sub { $CFG->{audio_enable} = $_[1]; 1 }
951     );
952    
953 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Sound Effects");
954 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
955 root 1.1 expand => 1, state => $CFG->{effects_enable},
956     tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
957     on_changed => sub {
958     $CFG->{effects_enable} = $_[1];
959     $CONN->update_fx_want if $CONN;
960     1
961     }
962     );
963 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
964 root 1.1 expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
965     tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
966     . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
967     on_changed => sub { $CFG->{effects_volume} = $_[1]; 1 }
968     );
969    
970 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Background Music");
971 root 1.18 $table->add_at (1, $row, new DC::UI::CheckBox
972 root 1.1 expand => 1, state => $CFG->{bgm_enable},
973     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
974     on_changed => sub {
975     $CFG->{bgm_enable} = $_[1];
976     $CONN->update_fx_want if $CONN;
977     audio_music_push;
978     1
979     }
980     );
981 root 1.18 $table->add_at (2, $row++, new DC::UI::Slider
982 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
983     tooltip => "The volume of the background music. Changes are instant.",
984     on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
985     );
986    
987 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Frequency");
988 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
989 root 1.1 c_colspan => 2, expand => 1,
990     value => $CFG->{audio_hw_frequency},
991     options => [
992     [ 0, "default" , "Use System Default"],
993     [11025, "11 kHz" , "11kHz (low quality)"],
994     [22050, "22 kHz" , "22kHz (reduced quality)"],
995     [44100, "44.1 kHz", "44.1kHz (cd quality)"],
996     [48000, "48 kHz" , "48kHz (studio quality)"],
997     ],
998     tooltip => "The sampling frequency to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
999     on_changed => sub {
1000     $CFG->{audio_hw_frequency} = $_[1];
1001     audio_tab_update;
1002     1
1003     }
1004     );
1005    
1006 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Channels");
1007 root 1.18 $table->add_at (1, $row++, new DC::UI::Selector
1008 root 1.1 c_colspan => 2, expand => 1,
1009     value => $CFG->{audio_hw_channels},
1010     options => [
1011     [0, "default" , "Use System Default"],
1012     [1, "Mono" , "Mono (single channel, low quality)"],
1013 root 1.6 [2, "Stereo" , "Stereo (dual channel, standard quality)"],
1014 root 1.1 [4, "4 Ch Surround", "4 Channel Surround Sound (3d sound, high quality)"],
1015     [6, "6 Ch Surround", "6 Channel Surround Sound (3d sound + center + lfe)"],
1016     ],
1017     tooltip => "The number of independent sound channels to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1018     on_changed => sub {
1019     $CFG->{audio_hw_channels} = $_[1];
1020     audio_tab_update;
1021     1
1022     }
1023     );
1024    
1025 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Latency");
1026 root 1.18 $table->add_at (1, $row++, $AUDIO_HW_CHUNKSIZE = new DC::UI::Selector
1027 root 1.1 c_colspan => 2, expand => 1,
1028     value => $CFG->{audio_hw_chunksize},
1029     tooltip => "The guarenteed latency. Lower is better, but also more cpu-intensive and might cause stuttering. If music playback "
1030     . "is stuttering, increase this value. Values of 50-100ms are optimal.",
1031     on_changed => sub {
1032     $CFG->{audio_hw_chunksize} = $_[1];
1033     audio_tab_update;
1034     1
1035     }
1036     );
1037    
1038     # should really be a slider
1039 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Mixer Voices");
1040 root 1.18 $table->add_at (1, $row++, new DC::UI::ValSlider
1041 root 1.1 c_colspan => 2, expand => 1,
1042     tooltip => "The number of simultaneous sound effects possible. Higher is better, but also more cpu-intensive and might cause stuttering.",
1043     range => [$::CFG->{audio_mix_channels}, 4, 32, 0, 1],
1044     template => ">= 99",
1045     on_changed => sub {
1046     my ($slider, $value) = @_;
1047    
1048     $CFG->{audio_mix_channels} = $value
1049     if $value;
1050     1;
1051     }
1052     );
1053    
1054 root 1.18 $table->add_at (1, $row++, new DC::UI::Button
1055 root 1.22 c_colspan => 2, expand => 1, text => "Apply",
1056 root 1.1 tooltip => "Apply the audio settings",
1057     on_activate => sub {
1058     audio_shutdown ();
1059     audio_init ();
1060     0
1061     }
1062     );
1063    
1064 root 1.18 $vbox->add (new DC::UI::FancyFrame
1065 root 1.1 expand => 1,
1066     label => "Audio Info",
1067 root 1.18 child => ($AUDIO_INFO = new DC::UI::Label ellipsise => 0),
1068 root 1.1 );
1069    
1070     audio_tab_update;
1071    
1072     $vbox
1073     }
1074    
1075     sub set_gauge_window_fontsize {
1076     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1077     $_->set_fontsize ($::CFG->{gauge_fontsize});
1078     }
1079     }
1080    
1081     sub make_gauge_window {
1082     my $gh = int $HEIGHT * $CFG->{gauge_size};
1083    
1084 root 1.18 my $win = new DC::UI::Frame (
1085 root 1.1 force_x => 0,
1086     force_y => "max",
1087     force_w => $WIDTH,
1088     force_h => $gh,
1089     );
1090    
1091 root 1.18 $win->add (my $hbox = new DC::UI::HBox
1092 root 1.1 children => [
1093 root 1.18 (new DC::UI::HBox expand => 1),
1094     (new DC::UI::VBox children => [
1095     (new DC::UI::Empty expand => 1),
1096     (new DC::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new DC::UI::Table)),
1097 root 1.1 ]),
1098 root 1.18 (my $vbox = new DC::UI::VBox),
1099 root 1.1 ],
1100     );
1101    
1102 root 1.18 $vbox->add (new DC::UI::HBox
1103 root 1.1 expand => 1,
1104     children => [
1105 root 1.18 (new DC::UI::Empty expand => 1),
1106     (my $hb = new DC::UI::HBox),
1107 root 1.1 ],
1108     );
1109    
1110 root 1.18 $hb->add (my $hg = new DC::UI::Gauge type => 'hp', tooltip => "#stat_health");
1111     $hb->add (my $mg = new DC::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1112     $hb->add (my $gg = new DC::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1113     $hb->add (my $fg = new DC::UI::Gauge type => 'food', tooltip => "#stat_food");
1114    
1115 root 1.22 $vbox->add (my $exp = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1116 root 1.18 $vbox->add (my $prg = new DC::UI::ExperienceProgress);
1117     $vbox->add (my $sklprg = new DC::UI::ExperienceProgress);
1118 root 1.22 $vbox->add (my $rng = new DC::UI::Label align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1119 root 1.1
1120     $GAUGES = {
1121     exp => $exp, prg => $prg, sklprg => $sklprg,
1122     win => $win, range => $rng,
1123     hp => $hg, mana => $mg, grace => $gg, food => $fg,
1124     };
1125    
1126     &set_gauge_window_fontsize;
1127    
1128     $win
1129     }
1130    
1131     sub debug_setup {
1132 root 1.18 my $table = new DC::UI::Table;
1133 root 1.1
1134 root 1.18 $table->add_at (0, 0, new DC::UI::Label text => "Widget Borders");
1135     $table->add_at (1, 0, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1136     $table->add_at (0, 1, new DC::UI::Label text => "Tooltip Widget Info");
1137     $table->add_at (1, 1, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1138     $table->add_at (0, 2, new DC::UI::Label text => "Show FPS");
1139     $table->add_at (1, 2, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1140     $table->add_at (0, 3, new DC::UI::Label text => "Suppress Tooltips");
1141     $table->add_at (1, 3, new DC::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1142     $table->add_at (0, 4, new DC::UI::Button text => "die on click(tm)", on_activate => sub { &DC::debug() } );
1143    
1144 root 1.21 $table->add_at (0, 5, new DC::UI::TextEdit text => "line1\0152\0153\nµikachu\nづx゙つ゛");#d#
1145 root 1.18
1146     $table->add_at (7,7, my $t = new DC::UI::Table expand => 0);
1147 root 1.20 $t->add_at (0,0, new DC::UI::Label text => "a a", c_rowspan => 1, c_colspan => 2);
1148     $t->add_at (2,0, new DC::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1149     $t->add_at (1,2, new DC::UI::Label text => "c c", c_rowspan => 1, c_colspan => 2);
1150     $t->add_at (0,1, new DC::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1, ellipsise => 0 );
1151 root 1.18 $t->add_at (1,1, new DC::UI::Label text => "e");
1152 root 1.1
1153 root 1.18 $table->add_at (7, 6, my $c = new DC::UI::Canvas);
1154 root 1.1
1155     $c->add_items ({
1156     type => "line_loop",
1157     color => [0, 1, 0],
1158     width => 9,
1159     coord_mode => "abs",
1160     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1161     });
1162    
1163     $c->add_items ({
1164     type => "lines",
1165     color => [1, 1, 0],
1166     width => 2,
1167     coord_mode => "rel",
1168     coord => [[0,0], [1,1], [1,0], [0,1]],
1169     });
1170    
1171     $c->add_items ({
1172     type => "polygon",
1173     color => [0, 0.43, 0],
1174     width => 2,
1175     coord_mode => "rel",
1176     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1177     });
1178    
1179     $table
1180     }
1181    
1182     sub stats_window {
1183 root 1.18 my $r = new DC::UI::ScrolledWindow (
1184 root 1.1 expand => 1,
1185     scroll_y => 1
1186     );
1187 root 1.18 $r->add (my $vb = new DC::UI::VBox);
1188 root 1.1
1189 root 1.18 $vb->add (new DC::UI::FancyFrame
1190 root 1.1 label => "Player",
1191 root 1.18 child => (my $pi = new DC::UI::VBox),
1192 root 1.1 );
1193    
1194 root 1.22 $pi->add ($STATWIDS->{title} = new DC::UI::Label text => "Title:", expand => 1, align => 0,
1195 root 1.1 can_hover => 1, can_events => 1,
1196     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1197 root 1.22 $pi->add ($STATWIDS->{map} = new DC::UI::Label align => 0, text => "Map:", expand => 1,
1198 root 1.1 can_hover => 1, can_events => 1,
1199     tooltip => "The map you are currently on (if supported by the server).");
1200    
1201 root 1.18 $pi->add (my $hb0 = new DC::UI::HBox);
1202 root 1.22 $hb0->add ($STATWIDS->{weight} = new DC::UI::Label text => "Weight:", expand => 1, align => 0,
1203 root 1.1 can_hover => 1, can_events => 1,
1204     tooltip => "The weight of the player including all inventory items.");
1205 root 1.22 $hb0->add ($STATWIDS->{m_weight} = new DC::UI::Label align => 0, text => "Max weight:", expand => 1,
1206 root 1.1 can_hover => 1, can_events => 1,
1207     tooltip => "The weight limit: you cannot carry more than this.");
1208    
1209 root 1.18 $vb->add (new DC::UI::FancyFrame
1210 root 1.1 label => "Primary/Secondary Statistics",
1211 root 1.18 child => (my $hb = new DC::UI::HBox expand => 1),
1212 root 1.1 );
1213 root 1.18 $hb->add (my $tbl = new DC::UI::Table expand => 1);
1214 root 1.1
1215     my $color2 = [1, 1, 0];
1216    
1217     for (
1218     [0, 0, st_str => "Str", 30],
1219     [0, 1, st_dex => "Dex", 30],
1220     [0, 2, st_con => "Con", 30],
1221     [0, 3, st_int => "Int", 30],
1222     [0, 4, st_wis => "Wis", 30],
1223     [0, 5, st_pow => "Pow", 30],
1224     [0, 6, st_cha => "Cha", 30],
1225    
1226     [2, 0, st_wc => "Wc", -120],
1227     [2, 1, st_ac => "Ac", -120],
1228     [2, 2, st_dam => "Dam", 120],
1229     [2, 3, st_arm => "Arm", 120],
1230     [2, 4, st_spd => "Spd", 10.54],
1231     [2, 5, st_wspd => "WSp", 10.54],
1232     ) {
1233     my ($col, $row, $id, $label, $template) = @$_;
1234    
1235 root 1.18 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new DC::UI::Label
1236 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1,
1237     align => 1, template => $template, tooltip => "#stat_$label");
1238 root 1.18 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new DC::UI::Label
1239 root 1.22 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2,
1240     align => 0, text => $label, tooltip => "#stat_$label");
1241 root 1.1 }
1242    
1243 root 1.18 $vb->add (new DC::UI::FancyFrame
1244 root 1.1 label => "Resistancies",
1245 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]),
1246 root 1.1 );
1247    
1248     my $row = 0;
1249     my $col = 0;
1250    
1251     my %resist_names = (
1252     slow => ["Slow",
1253     "<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.)"],
1254     holyw => ["Holy Word",
1255     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1256     conf => ["Confusion",
1257     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1258     fire => ["Fire",
1259     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1260     depl => ["Depletion",
1261     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1262     magic => ["Magic",
1263     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1264     drain => ["Draining",
1265     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1266     acid => ["Acid",
1267     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1268     pois => ["Poison",
1269     "<b>Poison</b> (resistance to getting poisoned)"],
1270     para => ["Paralysation",
1271     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1272     deat => ["Death",
1273     "<b>Death</b> (resistance against death spells)"],
1274     phys => ["Physical",
1275     "<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.)"],
1276     blind => ["Blind",
1277     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1278     fear => ["Fear",
1279     "<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)"],
1280     tund => ["Turn undead",
1281     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1282     elec => ["Electricity",
1283     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1284     cold => ["Cold",
1285     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1286     ghit => ["Ghost hit",
1287     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1288     );
1289    
1290     for (qw/slow holyw conf fire depl magic
1291     drain acid pois para deat phys
1292     blind fear tund elec cold ghit/)
1293     {
1294 root 1.22 $tbl2->add_at ($col + 2, $row,
1295 root 1.1 $STATWIDS->{"res_$_"} =
1296 root 1.18 new DC::UI::Label
1297 root 1.1 font => $FONT_FIXED,
1298     template => "-100%",
1299 root 1.22 align => 1,
1300 root 1.1 can_events => 1,
1301     can_hover => 1,
1302     tooltip => $resist_names{$_}->[1],
1303     );
1304 root 1.18 $tbl2->add_at ($col + 1, $row, new DC::UI::Image
1305 root 1.1 font => $FONT_FIXED,
1306     can_hover => 1,
1307     can_events => 1,
1308     path => "ui/resist/resist_$_.png",
1309     tooltip => $resist_names{$_}->[1],
1310     );
1311 root 1.22 $tbl2->add_at ($col + 0, $row, new DC::UI::Label
1312 root 1.1 text => $resist_names{$_}->[0],
1313     font => $FONT_FIXED,
1314 root 1.22 align => 1,
1315 root 1.1 can_hover => 1,
1316     can_events => 1,
1317     tooltip => $resist_names{$_}->[1],
1318     );
1319    
1320     $row++;
1321     if ($row % 6 == 0) {
1322 root 1.22 $col += 4;
1323 root 1.1 $row = 0;
1324     }
1325     }
1326    
1327     #update_stats_window ({});
1328    
1329     $r
1330     }
1331    
1332     sub skill_window {
1333 root 1.18 my $sw = new DC::UI::ScrolledWindow (expand => 1);
1334 root 1.1
1335 root 1.18 $sw->add ($STATWIDS->{skill_tbl} = new DC::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1336 root 1.1
1337     $sw
1338     }
1339    
1340     sub formsep($) {
1341     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1342     }
1343    
1344     my $METASERVER_ATIME;
1345    
1346     sub update_metaserver {
1347     my ($metaserver_dialog) = @_;
1348    
1349     $METASERVER = $metaserver_dialog
1350     if defined $metaserver_dialog;
1351    
1352     return if $METASERVER_ATIME > time;
1353     $METASERVER_ATIME = time + 60;
1354    
1355     my $table = $METASERVER->{table};
1356     $table->clear;
1357 root 1.18 $table->add_at (0, 0, my $label = new DC::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1358 root 1.1
1359     my $ok = 0;
1360    
1361 root 1.18 DC::background {
1362     my $ua = DC::lwp_useragent;
1363 root 1.1
1364 root 1.18 DC::background_msg DC::decode_json +(DC::lwp_check $ua->get ($META_SERVER))->decoded_content;
1365 root 1.1 } sub {
1366     my ($msg) = @_;
1367     if ($msg) {
1368     $table->clear;
1369    
1370     my @tip = (
1371     "The current number of users logged in on the server.",
1372     "The hostname of the server.",
1373     "The time this server has been running without being restarted.",
1374     "Short information about this server provided by its admins.",
1375     );
1376     my @col = qw(#Users Host Uptime Version Description);
1377 root 1.18 $table->add_at ($_, 0, new DC::UI::Label
1378 root 1.22 can_hover => 1, can_events => 1, fg => [1, 1, 0],
1379 root 1.1 text => $col[$_], tooltip => $tip[$_])
1380     for 0 .. $#col;
1381    
1382 root 1.22 my @align = qw(1 0.5 1 1 0);
1383 root 1.1
1384     my $y = 0;
1385     for my $m (@{ $msg->{servers} }) {
1386     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1387     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1388    
1389     for ($desc) {
1390     s/<br>/\n/gi;
1391     s/<li>/\n· /gi;
1392     s/<.*?>//sgi;
1393     s/&amp;/&/g;
1394     s/&lt;/</g;
1395     s/&gt;/>/g;
1396     }
1397    
1398     $uptime = sprintf "%dd %02d:%02d:%02d",
1399     (int $uptime / 86400),
1400     (int $uptime / 3600) % 24,
1401     (int $uptime / 60) % 60,
1402     $uptime % 60;
1403    
1404     $m = [$users, $host, $uptime, $version, $desc];
1405    
1406     $y++;
1407    
1408 root 1.18 $table->add_at (scalar @$m, $y, new DC::UI::VBox children => [
1409     (new DC::UI::Button
1410 root 1.1 text => "Use",
1411     tooltip => "Put this server into the <b>Host:Port</b> field",
1412     on_activate => sub {
1413     $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1414     $METASERVER->hide;
1415     0
1416     },
1417     ),
1418 root 1.18 (new DC::UI::Empty expand => 1),
1419 root 1.1 ]);
1420    
1421 root 1.18 $table->add_at ($_, $y, new DC::UI::Label
1422 root 1.1 max_w => $::WIDTH * 0.4,
1423     ellipsise => 0,
1424     align => $align[$_],
1425     text => $m->[$_],
1426     tooltip => $tip[$_],
1427     fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1428     can_hover => 1,
1429     can_events => 1,
1430     fontsize => 0.8)
1431     for 0 .. $#$m;
1432     }
1433     } else {
1434     $ok or $label->set_text ("error while contacting metaserver");
1435     }
1436     };
1437    
1438     }
1439    
1440     sub metaserver_dialog {
1441 root 1.18 my $vbox = new DC::UI::VBox;
1442     my $table = new DC::UI::Table;
1443     $vbox->add (new DC::UI::ScrolledWindow expand => 1, child => $table);
1444 root 1.1
1445 root 1.18 my $dialog = new DC::UI::Toplevel
1446 root 1.1 title => "Server List",
1447     name => 'metaserver_dialog',
1448     x => 'center',
1449     y => 'center',
1450     z => 3,
1451     force_w => $::WIDTH * 0.9,
1452     force_h => $::HEIGHT * 0.7,
1453     child => $vbox,
1454     has_close_button => 1,
1455     table => $table,
1456     on_visibility_change => sub {
1457     update_metaserver ($_[0]) if $_[1];
1458     0
1459     },
1460     ;
1461    
1462     $dialog
1463     }
1464    
1465     sub login_setup {
1466 root 1.18 my $vbox = new DC::UI::VBox;
1467 root 1.1
1468 root 1.18 $vbox->add (new DC::UI::FancyFrame
1469 root 1.1 label => "Login Settings",
1470 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1471 root 1.1 );
1472    
1473 root 1.22 $table->add_at (0, 4, new DC::UI::Label align => 1, text => "Username");
1474 root 1.18 $table->add_at (1, 4, new DC::UI::Entry
1475 root 1.1 text => $CFG->{profile}{default}{user},
1476     tooltip => "The name of your character on the server.",
1477     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1478     );
1479    
1480 root 1.22 $table->add_at (0, 5, new DC::UI::Label align => 1, text => "Password");
1481 root 1.18 $table->add_at (1, 5, new DC::UI::Entry
1482 root 1.1 text => $CFG->{profile}{default}{password},
1483     hidden => 1,
1484     tooltip => "The password for your character.",
1485     on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1486     );
1487    
1488 root 1.18 $table->add_at (1, 11, $LOGIN_BUTTON = new DC::UI::Button
1489 root 1.1 expand => 1,
1490     text => "Login / Register",
1491     tooltip => "This button will either login to the account configured above or register a new account.",
1492     on_activate => sub {
1493     $CONN ? stop_game
1494     : start_game;
1495     1
1496     },
1497     );
1498    
1499 root 1.18 $vbox->add (new DC::UI::FancyFrame
1500 root 1.1 label => "Registering",
1501     min_h => 200,
1502 root 1.22 child => (new DC::UI::Label valign => 0, ellipsise => 0,
1503 root 1.1 markup =>
1504     "To register a new account, choose a username that hasn't been taken yet and "
1505     . "try to log-in. Follow the instructions in the Log tab in the message window.",
1506     ),
1507     );
1508    
1509     $vbox
1510     }
1511    
1512     sub server_setup {
1513 root 1.18 my $vbox = new DC::UI::VBox;
1514 root 1.1
1515 root 1.18 $vbox->add (new DC::UI::FancyFrame
1516 root 1.1 label => "Connection Settings",
1517 root 1.18 child => (my $table = new DC::UI::Table expand => 1, col_expand => [0, 1]),
1518 root 1.1 );
1519    
1520     my $row = 0;
1521    
1522 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Host:Port");
1523 root 1.1 {
1524 root 1.18 $table->add_at (1, $row, my $vbox = new DC::UI::VBox);
1525 root 1.1
1526     $vbox->add (
1527 root 1.18 $HOST_ENTRY = new DC::UI::Entry
1528 root 1.1 expand => 1,
1529     text => $CFG->{profile}{default}{host},
1530     tooltip => "The hostname or ip address of the Deliantra server to connect to",
1531     on_changed => sub {
1532     my ($self, $value) = @_;
1533     $CFG->{profile}{default}{host} = $value;
1534     1
1535     }
1536     );
1537    
1538 root 1.5 if (0) { #d# disabled
1539 root 1.18 $vbox->add (new DC::UI::Button
1540 root 1.1 expand => 1,
1541     text => "Server List",
1542     other => $METASERVER,
1543     tooltip => "Show a list of available crossfire servers",
1544     on_activate => sub { $METASERVER->toggle_visibility; 0 },
1545     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1546     );
1547 root 1.5 }#d#
1548 root 1.1 }
1549    
1550 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Map Size");
1551 root 1.18 $table->add_at (1, $row, new DC::UI::Slider
1552 root 1.1 force_w => 100,
1553     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1554     tooltip => "This is the size of the portion of the map update the server sends you. "
1555     . "If you set this to a high value you will be able to see further, "
1556     . "but you also increase bandwidth requirements and latency. "
1557     . "This option is only used once at log-in.",
1558     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1559     );
1560    
1561 root 1.22 $table->add_at (0, ++$row, new DC::UI::Label align => 1, text => "Output-Rate");
1562 root 1.18 $table->add_at (1, $row, new DC::UI::Entry
1563 root 1.1 text => $CFG->{output_rate},
1564     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1565     . "when sending data. When 0 or unset, the server "
1566     . "default will be used, which is usually around 100kb/s. Most servers will "
1567     . "dynamically find an optimal rate, so adjust this only when necessary.",
1568     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1569     );
1570    
1571 root 1.18 $vbox->add (new DC::UI::FancyFrame
1572 root 1.1 label => "Server Info",
1573 root 1.18 child => ($SERVER_INFO = new DC::UI::Label ellipsise => 0),
1574 root 1.1 );
1575    
1576     $vbox
1577     }
1578    
1579     sub client_setup {
1580 root 1.18 my $table = new DC::UI::Table expand => 1, col_expand => [0, 1];
1581 root 1.1
1582     my $row = 0;
1583    
1584 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Tip of the day");
1585 root 1.18 $table->add_at (1, $row++, new DC::UI::CheckBox
1586 root 1.1 state => $CFG->{show_tips},
1587     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1588     on_changed => sub {
1589     my ($self, $value) = @_;
1590     $CFG->{show_tips} = $value;
1591     0
1592     }
1593     );
1594    
1595 root 1.22 $table->add_at (0, $row, new DC::UI::Label align => 1, text => "Messages Window Size");
1596 root 1.18 $table->add_at (1, $row++, my $saycmd = new DC::UI::Entry
1597 root 1.1 text => $CFG->{logview_max_par},
1598     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1599     . "sends more messages than this number, older messages get removed to save memory and "
1600     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1601     on_changed => sub {
1602     my ($self, $value) = @_;
1603     $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1604     0
1605     },
1606     );
1607    
1608     $table
1609     }
1610    
1611     sub autopickup_setup {
1612 root 1.18 my $r = new DC::UI::ScrolledWindow (
1613 root 1.1 expand => 1,
1614     scroll_y => 1
1615     );
1616 root 1.18 $r->add (my $table = new DC::UI::Table
1617 root 1.1 row_expand => [0],
1618     col_expand => [0, 1, 0, 1],
1619     );
1620    
1621     for (
1622     ["General", 0, 0,
1623     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1624     ["Inhibit autopickup" => PICKUP_INHIBIT],
1625     ["Stop before pickup" => PICKUP_STOP],
1626     ["Debug autopickup" => PICKUP_DEBUG],
1627     ],
1628     ["Weapons", 0, 6,
1629     ["All weapons" => PICKUP_ALLWEAPON],
1630     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1631     ["Bows" => PICKUP_BOW],
1632     ["Arrows" => PICKUP_ARROW],
1633     ],
1634     ["Armour", 0, 12,
1635     ["Helmets" => PICKUP_HELMET],
1636     ["Shields" => PICKUP_SHIELD],
1637     ["Body Armour" => PICKUP_ARMOUR],
1638     ["Boots" => PICKUP_BOOTS],
1639     ["Gloves" => PICKUP_GLOVES],
1640     ["Cloaks" => PICKUP_CLOAK],
1641     ],
1642    
1643     ["Readables", 2, 0,
1644     ["Spellbooks" => PICKUP_SPELLBOOK],
1645     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1646     ["Normal Books/Scrolls" => PICKUP_READABLES],
1647     ],
1648     ["Misc", 2, 5,
1649     ["Food" => PICKUP_FOOD],
1650     ["Drinks" => PICKUP_DRINK],
1651     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1652     ["Keys" => PICKUP_KEY],
1653     ["Magical Items" => PICKUP_MAGICAL],
1654     ["Potions" => PICKUP_POTION],
1655     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1656     ["Ignore cursed" => PICKUP_NOT_CURSED],
1657     ["Jewelery" => PICKUP_JEWELS],
1658     ["Flesh" => PICKUP_FLESH],
1659     ],
1660     ["Weight/Value ratio", 2, 17]
1661     )
1662     {
1663     my ($title, $x, $y, @bits) = @$_;
1664 root 1.18 $table->add_at ($x, $y, new DC::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1665 root 1.1
1666     for (@bits) {
1667     ++$y;
1668    
1669     my $mask = $_->[1];
1670 root 1.18 $table->add_at ($x , $y, new DC::UI::Label text => $_->[0], align => 1, expand => 1);
1671     $table->add_at ($x+1, $y, my $checkbox = new DC::UI::CheckBox
1672 root 1.1 state => $::CFG->{pickup} & $mask,
1673     on_changed => sub {
1674     my ($box, $value) = @_;
1675    
1676     if ($value) {
1677     $::CFG->{pickup} |= $mask;
1678     } else {
1679     $::CFG->{pickup} &= ~$mask;
1680     }
1681    
1682     $::CONN->send_command ("pickup $::CFG->{pickup}")
1683     if defined $::CONN;
1684    
1685     0
1686     });
1687    
1688     ${$_->[2]} = $checkbox if $_->[2];
1689     }
1690     }
1691    
1692 root 1.18 $table->add_at (2, 18, new DC::UI::ValSlider
1693 root 1.1 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1694     template => ">= 99",
1695     to_value => sub { ">= " . 5 * $_[0] },
1696     on_changed => sub {
1697     my ($slider, $value) = @_;
1698    
1699     $::CFG->{pickup} &= ~0xF;
1700     $::CFG->{pickup} |= int $value
1701     if $value;
1702     1;
1703     });
1704    
1705 root 1.18 $table->add_at (3, 18, new DC::UI::Button
1706 root 1.1 text => "set",
1707     on_activate => sub {
1708     $::CONN->send_command ("pickup $::CFG->{pickup}")
1709     if defined $::CONN;
1710     0
1711     });
1712    
1713     $r
1714     }
1715    
1716     my %SORT_ORDER = (
1717     type => undef,
1718     mtime => sub {
1719     my $NOW = time;
1720     sort {
1721     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1722     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1723    
1724     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1725     or $btime <=> $atime
1726     or $a->{type} <=> $b->{type}
1727     } @_
1728     },
1729     weight => sub { sort {
1730     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1731     or $a->{type} <=> $b->{type}
1732     } @_ },
1733     );
1734    
1735     sub inventory_widget {
1736 root 1.18 my $hb = new DC::UI::HBox homogeneous => 1;
1737 root 1.1
1738 root 1.18 $hb->add (my $vb1 = new DC::UI::VBox);
1739 root 1.22 $vb1->add (new DC::UI::Label text => "Player");
1740 root 1.1
1741 root 1.18 $vb1->add (my $hb1 = new DC::UI::HBox);
1742 root 1.1
1743     use sort 'stable';
1744    
1745 root 1.18 $hb1->add (new DC::UI::Selector
1746 root 1.1 value => $::CFG->{inv_sort},
1747     options => [
1748     [type => "Type/Name"],
1749     [mtime => "Recent/Normal/Locked"],
1750     [weight => "Weight/Type"],
1751     ],
1752     on_changed => sub {
1753     $::CFG->{inv_sort} = $_[1];
1754     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1755     },
1756     );
1757 root 1.18 $hb1->add (new DC::UI::Label text => "Weight: ", align => 1, expand => 1);
1758 root 1.1 #TODO# update to weigh/maxweight
1759 root 1.22 $hb1->add ($STATWIDS->{i_weight} = new DC::UI::Label align => 0);
1760 root 1.1
1761 root 1.18 $vb1->add (my $sw1 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1762     $sw1->add ($INV = new DC::UI::Inventory);
1763 root 1.1 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1764    
1765 root 1.18 $hb->add (my $vb2 = new DC::UI::VBox);
1766 root 1.1
1767 root 1.18 $vb2->add ($INVR_HB = new DC::UI::HBox);
1768 root 1.1
1769 root 1.18 $vb2->add (my $sw2 = new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
1770     $sw2->add ($INVR = new DC::UI::Inventory);
1771 root 1.1
1772     # XXX: Call after $INVR = ... because set_opencont sets the items
1773 root 1.18 DC::Protocol::set_opencont ($::CONN, 0, "Floor");
1774 root 1.1
1775     $hb
1776     }
1777    
1778     sub media_window {
1779 root 1.18 my $vb = new DC::UI::VBox;
1780 root 1.1
1781 root 1.18 $vb->add (new DC::UI::FancyFrame
1782 root 1.1 label => "Currently playing music",
1783 root 1.18 child => new DC::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1784     child => ($MUSIC_PLAYING_WIDGET = new DC::UI::Label ellipsise => 0, fontsize => 0.8),
1785 root 1.1 );
1786    
1787 root 1.18 $vb->add (new DC::UI::FancyFrame
1788 root 1.1 label => "Other media used in this session",
1789     expand => 1,
1790 root 1.18 child => ($LICENSE_WIDGET = new DC::UI::TextScroller
1791 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1792     );
1793    
1794     $vb
1795     }
1796    
1797     sub add_license {
1798     my ($meta) = @_;
1799    
1800     $meta = $meta->{data}
1801     or return;
1802    
1803     $meta->{license} || $meta->{author} || $meta->{source}
1804     or return;
1805    
1806     $LICENSE_WIDGET->add_paragraph ({
1807     fg => [1, 1, 1, 1],
1808     markup => "<small>"
1809 root 1.18 . "<b>Name:</b> " . (DC::asxml $meta->{name}) . "\n"
1810     . "<b>Author:</b> " . (DC::asxml $meta->{author}) . "\n"
1811     . "<b>Source:</b> " . (DC::asxml $meta->{source}) . "\n"
1812     . "<b>License:</b> " . (DC::asxml $meta->{license}) . "\n"
1813 root 1.1 . "</small>",
1814     });
1815     $LICENSE_WIDGET->scroll_to_bottom;
1816     }
1817    
1818     sub toggle_player_page {
1819     my ($widget) = @_;
1820    
1821     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1822     $PL_WINDOW->hide;
1823     } else {
1824     $PL_NOTEBOOK->set_current_page ($widget);
1825     $PL_WINDOW->show;
1826     }
1827     }
1828    
1829     sub player_window {
1830 root 1.18 my $plwin = $PL_WINDOW = new DC::UI::Toplevel
1831 root 1.1 x => "center",
1832     y => "center",
1833     force_w => $WIDTH * 9/10,
1834     force_h => $HEIGHT * 9/10,
1835     title => "Player",
1836     name => "playerbook",
1837     has_close_button => 1
1838     ;
1839    
1840     my $ntb =
1841     $PL_NOTEBOOK =
1842 root 1.18 new DC::UI::Notebook expand => 1;
1843 root 1.1
1844     $ntb->add_tab (
1845     "Statistics (F2)" => $STATS_PAGE = stats_window,
1846     "Shows statistics, where all your Stats and Resistances are shown."
1847     );
1848     $ntb->add_tab (
1849     "Skills (F3)" => $SKILL_PAGE = skill_window,
1850     "Shows all your Skills."
1851     );
1852    
1853 root 1.18 my $spellsw = $SPELL_PAGE = new DC::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1854     $spellsw->add ($SPELL_LIST = new DC::UI::SpellList);
1855 root 1.1 $ntb->add_tab (
1856     "Spellbook (F4)" => $spellsw,
1857     "Displays all spells you have and lets you edit keyboard shortcuts for them."
1858     );
1859     $ntb->add_tab (
1860     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1861     "Toggles the inventory window, where you can manage your loot (or treasures :). "
1862     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1863     );
1864     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
1865     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1866    
1867     $ntb->add_tab (Media => media_window,
1868     "License, Author and Source info for media sent by the server.");
1869    
1870     $ntb->set_current_page ($INVENTORY_PAGE);
1871    
1872     $plwin->add ($ntb);
1873     $plwin
1874     }
1875    
1876     sub keyboard_setup {
1877 root 1.18 DC::Macro::keyboard_setup
1878 root 1.1 }
1879    
1880     sub help_window {
1881 root 1.18 my $win = new DC::UI::Toplevel
1882 root 1.1 x => 'center',
1883     y => 'center',
1884     z => 4,
1885     name => 'doc_browser',
1886     force_w => int $WIDTH * 7/8,
1887     force_h => int $HEIGHT * 7/8,
1888     title => "Help Browser",
1889     has_close_button => 1;
1890    
1891 root 1.18 $win->add (my $vbox = new DC::UI::VBox);
1892 root 1.1
1893 root 1.18 $vbox->add (new DC::UI::FancyFrame
1894 root 1.1 label => "Navigation",
1895 root 1.18 child => (my $buttons = new DC::UI::HBox),
1896 root 1.1 );
1897 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
1898 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1899    
1900     my @history;
1901     my @future;
1902     my $curnode;
1903    
1904     my $load_node; $load_node = sub {
1905     my ($node, $para) = @_;
1906    
1907     $buttons->clear;
1908    
1909 root 1.18 $buttons->add (new DC::UI::Button
1910 root 1.1 text => "⇤",
1911     tooltip => "back to the starting page",
1912     on_activate => sub {
1913     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1914     unshift @future, @history;
1915     @history = ();
1916     $load_node->(@{shift @future});
1917     },
1918     );
1919    
1920     if (@history) {
1921 root 1.18 $buttons->add (new DC::UI::Button
1922 root 1.1 text => "⋘",
1923 root 1.18 tooltip => "back to <i>" . (DC::asxml DC::Pod::full_path $history[-1][0]) . "</i>",
1924 root 1.1 on_activate => sub {
1925     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1926     $load_node->(@{pop @history});
1927     },
1928     );
1929     }
1930    
1931     if (@future) {
1932 root 1.18 $buttons->add (new DC::UI::Button
1933 root 1.1 text => "⋙",
1934 root 1.18 tooltip => "forward to <i>" . (DC::asxml DC::Pod::full_path $future[0][0]) . "</i>",
1935 root 1.1 on_activate => sub {
1936     push @history, [$curnode, $viewer->current_paragraph];
1937     $load_node->(@{shift @future});
1938     },
1939     );
1940     }
1941    
1942 root 1.18 $buttons->add (new DC::UI::Label text => " ");
1943 root 1.1
1944 root 1.18 my @path = DC::Pod::full_path_of $node;
1945 root 1.1 pop @path; # drop current node
1946    
1947     for my $node (@path) {
1948 root 1.18 $buttons->add (new DC::UI::Button
1949 root 1.1 text => $node->{kw}[0],
1950 root 1.18 tooltip => "go to <i>" . (DC::asxml DC::Pod::full_path $node) . "</i>",
1951 root 1.1 on_activate => sub {
1952     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1953     $load_node->($node);
1954     },
1955     );
1956 root 1.18 $buttons->add (new DC::UI::Label text => "/");
1957 root 1.1 }
1958    
1959 root 1.18 $buttons->add (new DC::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1960 root 1.1
1961     $curnode = $node;
1962    
1963     $viewer->clear;
1964 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $curnode);
1965 root 1.1 $viewer->scroll_to ($para);
1966     };
1967    
1968 root 1.18 $load_node->(DC::Pod::find pod => "mainpage");
1969 root 1.1
1970 root 1.18 $DC::Pod::goto_document = sub {
1971 root 1.1 my (@path) = @_;
1972    
1973     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1974    
1975 root 1.18 $load_node->((DC::Pod::find @path)[0]);
1976 root 1.1 $win->show;
1977     };
1978    
1979     $win
1980     }
1981    
1982     sub open_string_query {
1983     my ($title, $cb, $txt, $tooltip) = @_;
1984 root 1.18 my $dialog = new DC::UI::Toplevel
1985 root 1.1 x => "center",
1986     y => "center",
1987     z => 50,
1988     force_w => $WIDTH * 4/5,
1989     title => $title;
1990    
1991     $dialog->add (
1992 root 1.18 my $e = new DC::UI::Entry
1993 root 1.1 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1994     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1995     tooltip => $tooltip
1996     );
1997    
1998     $e->grab_focus;
1999     $e->set_text ($txt) if $txt;
2000     $dialog->show;
2001     }
2002    
2003     sub open_quit_dialog {
2004     unless ($QUIT_DIALOG) {
2005 root 1.18 $QUIT_DIALOG = new DC::UI::Toplevel
2006 root 1.1 x => "center",
2007     y => "center",
2008     z => 50,
2009     title => "Really Quit?",
2010     on_key_down => sub {
2011     my ($dialog, $ev) = @_;
2012     $ev->{sym} == 27 and $dialog->hide;
2013     }
2014     ;
2015    
2016 root 1.18 $QUIT_DIALOG->add (my $vb = new DC::UI::VBox expand => 1);
2017 root 1.1
2018 root 1.18 $vb->add (new DC::UI::Label
2019 root 1.1 text => "You should find a savebed and apply it first!",
2020     max_w => $WIDTH * 0.25,
2021     ellipsize => 0,
2022     );
2023 root 1.18 $vb->add (my $hb = new DC::UI::HBox expand => 1);
2024     $hb->add (new DC::UI::Button
2025 root 1.1 text => "Ok",
2026     expand => 1,
2027     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2028     );
2029 root 1.18 $hb->add (new DC::UI::Button
2030 root 1.1 text => "Quit anyway",
2031     expand => 1,
2032 root 1.4 on_activate => sub { EV::unloop EV::UNLOOP_ALL },
2033 root 1.1 );
2034     }
2035    
2036     $QUIT_DIALOG->show;
2037     $QUIT_DIALOG->grab_focus;
2038     }
2039    
2040     sub show_tip_of_the_day {
2041     # find all tips
2042 root 1.18 my @tod = DC::Pod::find tip_of_the_day => "*";
2043 root 1.1
2044 root 1.18 DC::DB::get state => "tip_of_the_day", sub {
2045 root 1.1 my ($todindex) = @_;
2046     $todindex = 0 if $todindex >= @tod;
2047 root 1.18 DC::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2048 root 1.1
2049     # create dialog
2050     my $dialog;
2051    
2052     my $close = sub {
2053     $dialog->destroy;
2054     };
2055    
2056 root 1.18 $dialog = new DC::UI::Toplevel
2057 root 1.1 x => "center",
2058     y => "center",
2059     z => 3,
2060     name => 'tip_of_the_day',
2061     force_w => int $WIDTH * 4/9,
2062     force_h => int $WIDTH * 2/9,
2063     title => "Tip of the day #" . (1 + $todindex),
2064 root 1.18 child => my $vbox = new DC::UI::VBox,
2065 root 1.1 has_close_button => 1,
2066     on_delete => $close,
2067     ;
2068    
2069 root 1.18 $vbox->add (my $viewer = new DC::UI::TextScroller
2070 root 1.1 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2071 root 1.18 $viewer->add_paragraph (DC::Pod::as_paragraphs DC::Pod::section_of $tod[$todindex]);
2072 root 1.1
2073 root 1.18 $vbox->add (my $table = new DC::UI::Table col_expand => [0, 1]);
2074 root 1.1
2075 root 1.18 $table->add_at (0, 0, new DC::UI::Button
2076 root 1.1 text => "Close",
2077     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>.",
2078     on_activate => $close,
2079     );
2080    
2081 root 1.18 $table->add_at (2, 0, new DC::UI::Button
2082 root 1.1 text => "Next",
2083     tooltip => "Show the next <b>Tip of the day</b>.",
2084     on_activate => sub {
2085     $close->();
2086     &show_tip_of_the_day;
2087     },
2088     );
2089    
2090     $dialog->show;
2091     };
2092     }
2093    
2094     sub sdl_init {
2095 root 1.18 DC::SDL_Init
2096 root 1.1 and die "SDL::Init failed!\n";
2097     }
2098    
2099     sub video_init {
2100     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
2101    
2102     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2103    
2104     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2105     $FULLSCREEN = $CFG->{fullscreen};
2106     $FAST = $CFG->{fast};
2107    
2108 root 1.18 DC::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2109     or die "SDL_SetVideoMode failed: " . (DC::SDL_GetError) . "\n";
2110 root 1.1
2111     $SDL_ACTIVE = 1;
2112     $LAST_REFRESH = time - 0.01;
2113    
2114 root 1.18 DC::OpenGL::init;
2115     DC::Macro::init;
2116 root 1.1
2117     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2118    
2119 root 1.18 $DC::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2120 root 1.1
2121     #############################################################################
2122    
2123     if ($DEBUG_STATUS) {
2124 root 1.18 DC::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2125 root 1.1 } else {
2126     # create/configure the widgets
2127    
2128 root 1.18 $DC::UI::ROOT->connect (key_down => sub {
2129 root 1.1 my (undef, $ev) = @_;
2130    
2131 root 1.18 if (my @macros = DC::Macro::find $ev) {
2132     DC::Macro::execute $_ for @macros;
2133 root 1.1
2134     return 1;
2135     }
2136    
2137     0
2138     });
2139    
2140 root 1.18 $DEBUG_STATUS = new DC::UI::Label
2141 root 1.1 padding => 0,
2142     z => 100,
2143     force_x => "max",
2144     force_y => 0;
2145     $DEBUG_STATUS->show;
2146    
2147 root 1.18 $STATUSBOX = new DC::UI::Statusbox;
2148 root 1.1 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
2149    
2150 root 1.18 (new DC::UI::Frame
2151 root 1.1 bg => [0, 0, 0, 0.4],
2152     force_x => 0,
2153     force_y => "max",
2154     child => $STATUSBOX,
2155     )->show;
2156    
2157 root 1.18 DC::UI::Toplevel->new (
2158 root 1.2 title => "Minimap",
2159 root 1.1 name => "mapmap",
2160     x => 0,
2161     y => $FONTSIZE + 8,
2162     border_bg => [1, 1, 1, 192/255],
2163     bg => [1, 1, 1, 0],
2164 root 1.18 child => ($MAPMAP = new DC::MapWidget::MapMap
2165 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2166     ),
2167     )->show;
2168    
2169 root 1.18 $MAPWIDGET = new DC::MapWidget;
2170 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2171     my ($mapwidget, $preset) = @_;
2172    
2173     $MESSAGE_WINDOW->activate_console ($preset)
2174     if $MESSAGE_WINDOW;
2175     });
2176     $MAPWIDGET->show;
2177     $MAPWIDGET->grab_focus;
2178    
2179 root 1.18 $COMPLETER = new DC::MapWidget::Command::
2180 root 1.1 command => { },
2181     tooltip => "#completer_help",
2182     ;
2183    
2184 root 1.18 $SETUP_DIALOG = new DC::UI::Toplevel
2185 root 1.1 title => "Setup",
2186     name => "setup_dialog",
2187     x => 'center',
2188     y => 'center',
2189     z => 2,
2190     force_w => $::WIDTH * 0.6,
2191     force_h => $::HEIGHT * 0.6,
2192     has_close_button => 1,
2193     ;
2194    
2195     $METASERVER = metaserver_dialog;
2196 root 1.18 $MESSAGE_WINDOW = new DC::UI::MessageWindow;
2197 root 1.1
2198 root 1.18 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new DC::UI::Notebook expand => 1, debug => 1,
2199     filter => new DC::UI::ScrolledWindow expand => 1, scroll_y => 1);
2200 root 1.1
2201     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2202     "Configure the server to play on, your username and password.");
2203     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2204     "Configure other server related options.");
2205     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2206     "Configure various client-specific settings.");
2207     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2208     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2209     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2210     "Configure the use of audio, sound effects and background music.");
2211     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2212     "Lets you define, edit and delete key bindings."
2213     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2214     . "with nothing set and the recording started. After doing the actions you "
2215     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2216     . "After pressing the combo the binding will be saved automatically and the "
2217     . "binding editor closes");
2218     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2219     "Some debuggin' options. Do not ask.");
2220    
2221 root 1.18 $BUTTONBAR = new DC::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2222 root 1.1
2223 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2224 root 1.1 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2225    
2226 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2227 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2228    
2229     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
2230    
2231 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Playerbook", other => player_window,
2232 root 1.1 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2233    
2234 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2235 root 1.1 text => "Save Config",
2236     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2237     on_activate => sub {
2238 root 1.18 $::CFG->{layout} = DC::UI::get_layout;
2239     DC::write_cfg "$Deliantra::VARDIR/client.cf";
2240 root 1.1 status "Configuration Saved";
2241     0
2242     },
2243     );
2244    
2245 root 1.18 $BUTTONBAR->add (new DC::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2246 root 1.1 tooltip => "View Documentation");
2247    
2248    
2249 root 1.18 $BUTTONBAR->add (new DC::UI::Button
2250 root 1.1 text => "Quit",
2251     tooltip => "Terminates the program",
2252     on_activate => sub {
2253     if ($CONN) {
2254     open_quit_dialog;
2255     } else {
2256 root 1.4 EV::unloop EV::UNLOOP_ALL;
2257 root 1.1 }
2258     0
2259     },
2260     );
2261    
2262     $BUTTONBAR->show;
2263     $SETUP_DIALOG->show;
2264     $MESSAGE_WINDOW->show;
2265     }
2266    
2267     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2268     }
2269    
2270     sub video_shutdown {
2271 root 1.18 DC::OpenGL::shutdown;
2272 root 1.1
2273     undef $SDL_ACTIVE;
2274     }
2275    
2276     my %animate_object;
2277     my $animate_timer;
2278    
2279     my $fps = 9;
2280    
2281     sub force_refresh {
2282     if ($ENV{CFPLUS_DEBUG} & 4) {
2283     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2284     debug sprintf "%3.2f", $fps;
2285     }
2286    
2287 root 1.16 undef $WANT_REFRESH;
2288     $_[0]->stop;
2289 root 1.12
2290 root 1.18 $DC::UI::ROOT->draw;
2291     DC::SDL_GL_SwapBuffers;
2292 root 1.1 $LAST_REFRESH = $NOW;
2293     }
2294    
2295 root 1.19 my $want_refresh = EV::prepare_ns \&force_refresh;
2296 root 1.1
2297 root 1.19 my $input = EV::periodic 0, 1 / $MAX_FPS, undef, sub {
2298     $NOW = EV::now;
2299 root 1.1
2300     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2301 root 1.18 for DC::poll_events;
2302 root 1.1
2303     if (%animate_object) {
2304     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2305 root 1.16 $WANT_REFRESH = 1;
2306 root 1.1 }
2307 root 1.16
2308     $want_refresh->start
2309     if $WANT_REFRESH;
2310 root 1.4 };
2311 root 1.1
2312     sub animation_start {
2313     my ($widget) = @_;
2314     $animate_object{$widget} = $widget;
2315     }
2316    
2317     sub animation_stop {
2318     my ($widget) = @_;
2319     delete $animate_object{$widget};
2320     }
2321    
2322     %SDL_CB = (
2323 root 1.18 DC::SDL_QUIT => sub {
2324 root 1.4 EV::unloop EV::UNLOOP_ALL;
2325 root 1.1 },
2326 root 1.18 DC::SDL_VIDEORESIZE => sub {
2327 root 1.1 },
2328 root 1.18 DC::SDL_VIDEOEXPOSE => sub {
2329     DC::UI::full_refresh;
2330 root 1.1 },
2331 root 1.18 DC::SDL_ACTIVEEVENT => sub {
2332 root 1.12 # not useful, as APPACTIVE includes only iconified state, not unmapped
2333 root 1.18 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, DC::SDL_GetAppState;#d#
2334     # printf "a %x\n", DC::SDL_GetAppState & DC::SDL_APPACTIVE;#d#
2335     # printf "A\n" if $_[0]{state} & DC::SDL_APPACTIVE;
2336     # printf "K\n" if $_[0]{state} & DC::SDL_APPINPUTFOCUS;
2337     # printf "M\n" if $_[0]{state} & DC::SDL_APPMOUSEFOCUS;
2338 root 1.1 },
2339 root 1.18 DC::SDL_KEYDOWN => sub {
2340     if ($_[0]{mod} & DC::KMOD_ALT && $_[0]{sym} == 13) {
2341 root 1.1 # alt-enter
2342     $FULLSCREEN_ENABLE->toggle;
2343     video_shutdown;
2344     video_init;
2345     } else {
2346 root 1.18 DC::UI::feed_sdl_key_down_event ($_[0]);
2347 root 1.1 }
2348     },
2349 root 1.18 DC::SDL_KEYUP => \&DC::UI::feed_sdl_key_up_event,
2350     DC::SDL_MOUSEMOTION => \&DC::UI::feed_sdl_motion_event,
2351     DC::SDL_MOUSEBUTTONDOWN => \&DC::UI::feed_sdl_button_down_event,
2352     DC::SDL_MOUSEBUTTONUP => \&DC::UI::feed_sdl_button_up_event,
2353     DC::SDL_USEREVENT => sub {
2354 root 1.1 if ($_[0]{code} == 1) {
2355     audio_channel_finished $_[0]{data1};
2356     } elsif ($_[0]{code} == 0) {
2357     audio_music_finished;
2358     }
2359     },
2360     );
2361    
2362     #############################################################################
2363    
2364 root 1.11 $SIG{INT} = $SIG{TERM} = sub {
2365     EV::unloop;
2366     #d# TODO calling exit here hangs the process in some futex
2367     };
2368 root 1.1
2369     {
2370 root 1.15 if (-e "$Deliantra::VARDIR/client.cf") {
2371 root 1.18 DC::read_cfg "$Deliantra::VARDIR/client.cf";
2372 root 1.15 } else {
2373     #TODO: compatibility cruft
2374 root 1.18 DC::read_cfg "$Deliantra::OLDDIR/cfplusrc";
2375 root 1.15 print STDERR "INFO: used old configuratrion file\n";
2376     }
2377    
2378 root 1.18 DC::DB::Server::run;
2379 root 1.1
2380 root 1.18 DC::UI::set_layout ($::CFG->{layout});
2381 root 1.1
2382     my %DEF_CFG = (
2383     sdl_mode => 0,
2384 root 1.3 fullscreen => 1,
2385 root 1.1 fast => 0,
2386     force_opengl11 => undef,
2387     texture_compression => 1,
2388     map_scale => 1,
2389     fow_enable => 1,
2390     fow_intensity => 0,
2391     map_smoothing => 1,
2392     gui_fontsize => 1,
2393     log_fontsize => 0.7,
2394     gauge_fontsize => 1,
2395     gauge_size => 0.35,
2396     stat_fontsize => 0.7,
2397     mapsize => 100,
2398     audio_enable => 1,
2399     audio_hw_channels => 0,
2400     audio_hw_frequency => 0,
2401     audio_hw_chunksize => 0,
2402     audio_mix_channels => 8,
2403     effects_enable => 1,
2404     effects_volume => 1,
2405     bgm_enable => 1,
2406     bgm_volume => 0.5,
2407     output_rate => "",
2408     pickup => 0,
2409     inv_sort => "mtime",
2410     default => "profile", # default profile
2411     show_tips => 1,
2412     logview_max_par => 1000,
2413     );
2414    
2415     while (my ($k, $v) = each %DEF_CFG) {
2416     $CFG->{$k} = $v unless exists $CFG->{$k};
2417     }
2418    
2419     $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2420     $PROFILE = $CFG->{profile}{default};
2421    
2422     # convert old bindings (only default profile matters)
2423     if (my $bindings = delete $PROFILE->{bindings}) {
2424     while (my ($mod, $syms) = each %$bindings) {
2425     while (my ($sym, $cmds) = each %$syms) {
2426     push @{ $PROFILE->{macro} }, {
2427     accelkey => [$mod*1, $sym*1],
2428     action => $cmds,
2429     };
2430     }
2431     }
2432     }
2433    
2434     sdl_init;
2435    
2436 root 1.18 @SDL_MODES = DC::SDL_ListModes 8, 8;
2437     @SDL_MODES = DC::SDL_ListModes 5, 0 unless @SDL_MODES;
2438     @SDL_MODES or DC::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2439 root 1.1
2440     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2441    
2442     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2443    
2444     {
2445 root 1.18 my @fonts = map DC::find_rcfile "fonts/$_", qw(
2446 root 1.1 DejaVuSans.ttf
2447     DejaVuSansMono.ttf
2448     DejaVuSans-Bold.ttf
2449     DejaVuSansMono-Bold.ttf
2450     DejaVuSans-Oblique.ttf
2451     DejaVuSansMono-Oblique.ttf
2452     DejaVuSans-BoldOblique.ttf
2453     DejaVuSansMono-BoldOblique.ttf
2454     );
2455    
2456 root 1.18 DC::add_font $_ for @fonts;
2457 root 1.1
2458 root 1.18 DC::pango_init;
2459 root 1.1
2460 root 1.18 $FONT_PROP = new_from_file DC::Font $fonts[0];
2461     $FONT_FIXED = new_from_file DC::Font $fonts[1];
2462 root 1.1
2463     $FONT_PROP->make_default;
2464     }
2465    
2466     # compare mono (ft) vs. rgba (cairo)
2467     # ft - 1.8s, cairo 3s, even in alpha-only mode
2468     # for my $rgba (0..1) {
2469     # my $t1 = Time::HiRes::time;
2470     # for (1..1000) {
2471 root 1.18 # my $layout = DC::Layout->new ($rgba);
2472 root 1.1 # $layout->set_text ("hallo" x 100);
2473     # $layout->render;
2474     # }
2475     # my $t2 = Time::HiRes::time;
2476     # warn $t2-$t1;
2477     # }
2478    
2479     video_init;
2480     audio_init;
2481     }
2482    
2483     show_tip_of_the_day if $CFG->{show_tips};
2484    
2485 root 1.4 our $STARTUP_CANCEL = EV::idle sub {
2486     undef $::STARTUP_CANCEL;
2487 root 1.1 $startup_done->();
2488 root 1.4 };
2489 root 1.1
2490 root 1.4 EV::loop;
2491 root 1.1
2492     #video_shutdown;
2493     #audio_shutdown;
2494 root 1.18 DC::OpenGL::quit;
2495     DC::SDL_Quit;
2496     DC::DB::Server::stop;
2497 root 1.1
2498     =head1 NAME
2499    
2500     deliantra - A Deliantra MORPG game client
2501    
2502     =head1 SYNOPSIS
2503    
2504     Just run it - no commandline arguments are supported.
2505    
2506     =head1 USAGE
2507    
2508     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2509     be used in fullscreen mode and interactively.
2510    
2511     =head1 DEBUGGING
2512    
2513    
2514     CFPLUS_DEBUG - environment variable
2515    
2516     1 draw borders around widgets
2517     2 add low-level widget info to tooltips
2518     4 show fps
2519     8 suppress tooltips
2520    
2521     =head1 AUTHOR
2522    
2523     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2524    
2525    
2526