ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.18
Committed: Wed Dec 26 21:03:21 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
Changes since 1.17: +369 -369 lines
Log Message:
initial module hiding

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