ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.1
Committed: Sun Oct 14 15:33:31 2007 UTC (16 years, 7 months ago) by root
Branch: MAIN
Log Message:
raw rename

File Contents

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