ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.4
Committed: Mon Nov 12 23:25:37 2007 UTC (16 years, 6 months ago) by root
Branch: MAIN
Changes since 1.3: +21 -24 lines
Log Message:
switch from Event to EV

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