ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/deliantra
Revision: 1.6
Committed: Mon Dec 3 22:37:10 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
Changes since 1.5: +1 -1 lines
Log Message:
*** empty log message ***

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 root 1.6 [2, "Stereo" , "Stereo (dual channel, standard quality)"],
996 root 1.1 [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 root 1.5 if (0) { #d# disabled
1523 root 1.1 $vbox->add (new CFPlus::UI::Button
1524     expand => 1,
1525     text => "Server List",
1526     other => $METASERVER,
1527     tooltip => "Show a list of available crossfire servers",
1528     on_activate => sub { $METASERVER->toggle_visibility; 0 },
1529     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1530     );
1531 root 1.5 }#d#
1532 root 1.1 }
1533    
1534     $table->add_at (0, ++$row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1535     $table->add_at (1, $row, new CFPlus::UI::Slider
1536     force_w => 100,
1537     range => [$CFG->{mapsize}, 10, 100, 0, 1],
1538     tooltip => "This is the size of the portion of the map update the server sends you. "
1539     . "If you set this to a high value you will be able to see further, "
1540     . "but you also increase bandwidth requirements and latency. "
1541     . "This option is only used once at log-in.",
1542     on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1543     );
1544    
1545     $table->add_at (0, ++$row, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
1546     $table->add_at (1, $row, new CFPlus::UI::Entry
1547     text => $CFG->{output_rate},
1548     tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1549     . "when sending data. When 0 or unset, the server "
1550     . "default will be used, which is usually around 100kb/s. Most servers will "
1551     . "dynamically find an optimal rate, so adjust this only when necessary.",
1552     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1553     );
1554    
1555     $vbox->add (new CFPlus::UI::FancyFrame
1556     label => "Server Info",
1557     child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1558     );
1559    
1560     $vbox
1561     }
1562    
1563     sub client_setup {
1564     my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
1565    
1566     my $row = 0;
1567    
1568     $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1569     $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1570     state => $CFG->{show_tips},
1571     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1572     on_changed => sub {
1573     my ($self, $value) = @_;
1574     $CFG->{show_tips} = $value;
1575     0
1576     }
1577     );
1578    
1579     $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1580     $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
1581     text => $CFG->{logview_max_par},
1582     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1583     . "sends more messages than this number, older messages get removed to save memory and "
1584     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1585     on_changed => sub {
1586     my ($self, $value) = @_;
1587     $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1588     0
1589     },
1590     );
1591    
1592     $table
1593     }
1594    
1595     sub autopickup_setup {
1596     my $r = new CFPlus::UI::ScrolledWindow (
1597     expand => 1,
1598     scroll_y => 1
1599     );
1600     $r->add (my $table = new CFPlus::UI::Table
1601     row_expand => [0],
1602     col_expand => [0, 1, 0, 1],
1603     );
1604    
1605     for (
1606     ["General", 0, 0,
1607     ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1608     ["Inhibit autopickup" => PICKUP_INHIBIT],
1609     ["Stop before pickup" => PICKUP_STOP],
1610     ["Debug autopickup" => PICKUP_DEBUG],
1611     ],
1612     ["Weapons", 0, 6,
1613     ["All weapons" => PICKUP_ALLWEAPON],
1614     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1615     ["Bows" => PICKUP_BOW],
1616     ["Arrows" => PICKUP_ARROW],
1617     ],
1618     ["Armour", 0, 12,
1619     ["Helmets" => PICKUP_HELMET],
1620     ["Shields" => PICKUP_SHIELD],
1621     ["Body Armour" => PICKUP_ARMOUR],
1622     ["Boots" => PICKUP_BOOTS],
1623     ["Gloves" => PICKUP_GLOVES],
1624     ["Cloaks" => PICKUP_CLOAK],
1625     ],
1626    
1627     ["Readables", 2, 0,
1628     ["Spellbooks" => PICKUP_SPELLBOOK],
1629     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1630     ["Normal Books/Scrolls" => PICKUP_READABLES],
1631     ],
1632     ["Misc", 2, 5,
1633     ["Food" => PICKUP_FOOD],
1634     ["Drinks" => PICKUP_DRINK],
1635     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1636     ["Keys" => PICKUP_KEY],
1637     ["Magical Items" => PICKUP_MAGICAL],
1638     ["Potions" => PICKUP_POTION],
1639     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1640     ["Ignore cursed" => PICKUP_NOT_CURSED],
1641     ["Jewelery" => PICKUP_JEWELS],
1642     ["Flesh" => PICKUP_FLESH],
1643     ],
1644     ["Weight/Value ratio", 2, 17]
1645     )
1646     {
1647     my ($title, $x, $y, @bits) = @$_;
1648     $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1649    
1650     for (@bits) {
1651     ++$y;
1652    
1653     my $mask = $_->[1];
1654     $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1655     $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1656     state => $::CFG->{pickup} & $mask,
1657     on_changed => sub {
1658     my ($box, $value) = @_;
1659    
1660     if ($value) {
1661     $::CFG->{pickup} |= $mask;
1662     } else {
1663     $::CFG->{pickup} &= ~$mask;
1664     }
1665    
1666     $::CONN->send_command ("pickup $::CFG->{pickup}")
1667     if defined $::CONN;
1668    
1669     0
1670     });
1671    
1672     ${$_->[2]} = $checkbox if $_->[2];
1673     }
1674     }
1675    
1676     $table->add_at (2, 18, new CFPlus::UI::ValSlider
1677     range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1678     template => ">= 99",
1679     to_value => sub { ">= " . 5 * $_[0] },
1680     on_changed => sub {
1681     my ($slider, $value) = @_;
1682    
1683     $::CFG->{pickup} &= ~0xF;
1684     $::CFG->{pickup} |= int $value
1685     if $value;
1686     1;
1687     });
1688    
1689     $table->add_at (3, 18, new CFPlus::UI::Button
1690     text => "set",
1691     on_activate => sub {
1692     $::CONN->send_command ("pickup $::CFG->{pickup}")
1693     if defined $::CONN;
1694     0
1695     });
1696    
1697     $r
1698     }
1699    
1700     my %SORT_ORDER = (
1701     type => undef,
1702     mtime => sub {
1703     my $NOW = time;
1704     sort {
1705     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1706     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1707    
1708     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1709     or $btime <=> $atime
1710     or $a->{type} <=> $b->{type}
1711     } @_
1712     },
1713     weight => sub { sort {
1714     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1715     or $a->{type} <=> $b->{type}
1716     } @_ },
1717     );
1718    
1719     sub inventory_widget {
1720     my $hb = new CFPlus::UI::HBox homogeneous => 1;
1721    
1722     $hb->add (my $vb1 = new CFPlus::UI::VBox);
1723     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1724    
1725     $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1726    
1727     use sort 'stable';
1728    
1729     $hb1->add (new CFPlus::UI::Selector
1730     value => $::CFG->{inv_sort},
1731     options => [
1732     [type => "Type/Name"],
1733     [mtime => "Recent/Normal/Locked"],
1734     [weight => "Weight/Type"],
1735     ],
1736     on_changed => sub {
1737     $::CFG->{inv_sort} = $_[1];
1738     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1739     },
1740     );
1741     $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1742     #TODO# update to weigh/maxweight
1743     $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1744    
1745     $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1746     $sw1->add ($INV = new CFPlus::UI::Inventory);
1747     $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1748    
1749     $hb->add (my $vb2 = new CFPlus::UI::VBox);
1750    
1751     $vb2->add ($INVR_HB = new CFPlus::UI::HBox);
1752    
1753     $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1754     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1755    
1756     # XXX: Call after $INVR = ... because set_opencont sets the items
1757     CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1758    
1759     $hb
1760     }
1761    
1762     sub media_window {
1763     my $vb = new CFPlus::UI::VBox;
1764    
1765     $vb->add (new CFPlus::UI::FancyFrame
1766     label => "Currently playing music",
1767     child => new CFPlus::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1768     child => ($MUSIC_PLAYING_WIDGET = new CFPlus::UI::Label ellipsise => 0, fontsize => 0.8),
1769     );
1770    
1771     $vb->add (new CFPlus::UI::FancyFrame
1772     label => "Other media used in this session",
1773     expand => 1,
1774     child => ($LICENSE_WIDGET = new CFPlus::UI::TextScroller
1775     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1776     );
1777    
1778     $vb
1779     }
1780    
1781     sub add_license {
1782     my ($meta) = @_;
1783    
1784     $meta = $meta->{data}
1785     or return;
1786    
1787     $meta->{license} || $meta->{author} || $meta->{source}
1788     or return;
1789    
1790     $LICENSE_WIDGET->add_paragraph ({
1791     fg => [1, 1, 1, 1],
1792     markup => "<small>"
1793     . "<b>Name:</b> " . (CFPlus::asxml $meta->{name}) . "\n"
1794     . "<b>Author:</b> " . (CFPlus::asxml $meta->{author}) . "\n"
1795     . "<b>Source:</b> " . (CFPlus::asxml $meta->{source}) . "\n"
1796     . "<b>License:</b> " . (CFPlus::asxml $meta->{license}) . "\n"
1797     . "</small>",
1798     });
1799     $LICENSE_WIDGET->scroll_to_bottom;
1800     }
1801    
1802     sub toggle_player_page {
1803     my ($widget) = @_;
1804    
1805     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1806     $PL_WINDOW->hide;
1807     } else {
1808     $PL_NOTEBOOK->set_current_page ($widget);
1809     $PL_WINDOW->show;
1810     }
1811     }
1812    
1813     sub player_window {
1814     my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1815     x => "center",
1816     y => "center",
1817     force_w => $WIDTH * 9/10,
1818     force_h => $HEIGHT * 9/10,
1819     title => "Player",
1820     name => "playerbook",
1821     has_close_button => 1
1822     ;
1823    
1824     my $ntb =
1825     $PL_NOTEBOOK =
1826     new CFPlus::UI::Notebook expand => 1;
1827    
1828     $ntb->add_tab (
1829     "Statistics (F2)" => $STATS_PAGE = stats_window,
1830     "Shows statistics, where all your Stats and Resistances are shown."
1831     );
1832     $ntb->add_tab (
1833     "Skills (F3)" => $SKILL_PAGE = skill_window,
1834     "Shows all your Skills."
1835     );
1836    
1837     my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1838     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1839     $ntb->add_tab (
1840     "Spellbook (F4)" => $spellsw,
1841     "Displays all spells you have and lets you edit keyboard shortcuts for them."
1842     );
1843     $ntb->add_tab (
1844     "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1845     "Toggles the inventory window, where you can manage your loot (or treasures :). "
1846     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1847     );
1848     $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
1849     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1850    
1851     $ntb->add_tab (Media => media_window,
1852     "License, Author and Source info for media sent by the server.");
1853    
1854     $ntb->set_current_page ($INVENTORY_PAGE);
1855    
1856     $plwin->add ($ntb);
1857     $plwin
1858     }
1859    
1860     sub keyboard_setup {
1861     CFPlus::Macro::keyboard_setup
1862     }
1863    
1864     sub help_window {
1865     my $win = new CFPlus::UI::Toplevel
1866     x => 'center',
1867     y => 'center',
1868     z => 4,
1869     name => 'doc_browser',
1870     force_w => int $WIDTH * 7/8,
1871     force_h => int $HEIGHT * 7/8,
1872     title => "Help Browser",
1873     has_close_button => 1;
1874    
1875     $win->add (my $vbox = new CFPlus::UI::VBox);
1876    
1877     $vbox->add (new CFPlus::UI::FancyFrame
1878     label => "Navigation",
1879     child => (my $buttons = new CFPlus::UI::HBox),
1880     );
1881     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1882     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1883    
1884     my @history;
1885     my @future;
1886     my $curnode;
1887    
1888     my $load_node; $load_node = sub {
1889     my ($node, $para) = @_;
1890    
1891     $buttons->clear;
1892    
1893     $buttons->add (new CFPlus::UI::Button
1894     text => "⇤",
1895     tooltip => "back to the starting page",
1896     on_activate => sub {
1897     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1898     unshift @future, @history;
1899     @history = ();
1900     $load_node->(@{shift @future});
1901     },
1902     );
1903    
1904     if (@history) {
1905     $buttons->add (new CFPlus::UI::Button
1906     text => "⋘",
1907     tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1908     on_activate => sub {
1909     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1910     $load_node->(@{pop @history});
1911     },
1912     );
1913     }
1914    
1915     if (@future) {
1916     $buttons->add (new CFPlus::UI::Button
1917     text => "â‹™",
1918     tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1919     on_activate => sub {
1920     push @history, [$curnode, $viewer->current_paragraph];
1921     $load_node->(@{shift @future});
1922     },
1923     );
1924     }
1925    
1926     $buttons->add (new CFPlus::UI::Label text => " ");
1927    
1928     my @path = CFPlus::Pod::full_path_of $node;
1929     pop @path; # drop current node
1930    
1931     for my $node (@path) {
1932     $buttons->add (new CFPlus::UI::Button
1933     text => $node->{kw}[0],
1934     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1935     on_activate => sub {
1936     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1937     $load_node->($node);
1938     },
1939     );
1940     $buttons->add (new CFPlus::UI::Label text => "/");
1941     }
1942    
1943     $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1944    
1945     $curnode = $node;
1946    
1947     $viewer->clear;
1948     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1949     $viewer->scroll_to ($para);
1950     };
1951    
1952     $load_node->(CFPlus::Pod::find pod => "mainpage");
1953    
1954     $CFPlus::Pod::goto_document = sub {
1955     my (@path) = @_;
1956    
1957     push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1958    
1959     $load_node->((CFPlus::Pod::find @path)[0]);
1960     $win->show;
1961     };
1962    
1963     $win
1964     }
1965    
1966     sub open_string_query {
1967     my ($title, $cb, $txt, $tooltip) = @_;
1968     my $dialog = new CFPlus::UI::Toplevel
1969     x => "center",
1970     y => "center",
1971     z => 50,
1972     force_w => $WIDTH * 4/5,
1973     title => $title;
1974    
1975     $dialog->add (
1976     my $e = new CFPlus::UI::Entry
1977     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1978     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1979     tooltip => $tooltip
1980     );
1981    
1982     $e->grab_focus;
1983     $e->set_text ($txt) if $txt;
1984     $dialog->show;
1985     }
1986    
1987     sub open_quit_dialog {
1988     unless ($QUIT_DIALOG) {
1989     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1990     x => "center",
1991     y => "center",
1992     z => 50,
1993     title => "Really Quit?",
1994     on_key_down => sub {
1995     my ($dialog, $ev) = @_;
1996     $ev->{sym} == 27 and $dialog->hide;
1997     }
1998     ;
1999    
2000     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
2001    
2002     $vb->add (new CFPlus::UI::Label
2003     text => "You should find a savebed and apply it first!",
2004     max_w => $WIDTH * 0.25,
2005     ellipsize => 0,
2006     );
2007     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
2008     $hb->add (new CFPlus::UI::Button
2009     text => "Ok",
2010     expand => 1,
2011     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2012     );
2013     $hb->add (new CFPlus::UI::Button
2014     text => "Quit anyway",
2015     expand => 1,
2016 root 1.4 on_activate => sub { EV::unloop EV::UNLOOP_ALL },
2017 root 1.1 );
2018     }
2019    
2020     $QUIT_DIALOG->show;
2021     $QUIT_DIALOG->grab_focus;
2022     }
2023    
2024     sub show_tip_of_the_day {
2025     # find all tips
2026     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
2027    
2028     CFPlus::DB::get state => "tip_of_the_day", sub {
2029     my ($todindex) = @_;
2030     $todindex = 0 if $todindex >= @tod;
2031     CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2032    
2033     # create dialog
2034     my $dialog;
2035    
2036     my $close = sub {
2037     $dialog->destroy;
2038     };
2039    
2040     $dialog = new CFPlus::UI::Toplevel
2041     x => "center",
2042     y => "center",
2043     z => 3,
2044     name => 'tip_of_the_day',
2045     force_w => int $WIDTH * 4/9,
2046     force_h => int $WIDTH * 2/9,
2047     title => "Tip of the day #" . (1 + $todindex),
2048     child => my $vbox = new CFPlus::UI::VBox,
2049     has_close_button => 1,
2050     on_delete => $close,
2051     ;
2052    
2053     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
2054     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2055     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
2056    
2057     $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
2058    
2059     $table->add_at (0, 0, new CFPlus::UI::Button
2060     text => "Close",
2061     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>.",
2062     on_activate => $close,
2063     );
2064    
2065     $table->add_at (2, 0, new CFPlus::UI::Button
2066     text => "Next",
2067     tooltip => "Show the next <b>Tip of the day</b>.",
2068     on_activate => sub {
2069     $close->();
2070     &show_tip_of_the_day;
2071     },
2072     );
2073    
2074     $dialog->show;
2075     };
2076     }
2077    
2078     sub sdl_init {
2079     CFPlus::SDL_Init
2080     and die "SDL::Init failed!\n";
2081     }
2082    
2083     sub video_init {
2084     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
2085    
2086     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2087    
2088     ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2089     $FULLSCREEN = $CFG->{fullscreen};
2090     $FAST = $CFG->{fast};
2091    
2092     CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2093     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
2094    
2095     $SDL_ACTIVE = 1;
2096     $LAST_REFRESH = time - 0.01;
2097    
2098     CFPlus::OpenGL::init;
2099     CFPlus::Macro::init;
2100    
2101     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2102    
2103     $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2104    
2105     #############################################################################
2106    
2107     if ($DEBUG_STATUS) {
2108     CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2109     } else {
2110     # create/configure the widgets
2111    
2112     $CFPlus::UI::ROOT->connect (key_down => sub {
2113     my (undef, $ev) = @_;
2114    
2115     if (my @macros = CFPlus::Macro::find $ev) {
2116     CFPlus::Macro::execute $_ for @macros;
2117    
2118     return 1;
2119     }
2120    
2121     0
2122     });
2123    
2124     $DEBUG_STATUS = new CFPlus::UI::Label
2125     padding => 0,
2126     z => 100,
2127     force_x => "max",
2128     force_y => 0;
2129     $DEBUG_STATUS->show;
2130    
2131     $STATUSBOX = new CFPlus::UI::Statusbox;
2132     $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
2133    
2134     (new CFPlus::UI::Frame
2135     bg => [0, 0, 0, 0.4],
2136     force_x => 0,
2137     force_y => "max",
2138     child => $STATUSBOX,
2139     )->show;
2140    
2141     CFPlus::UI::Toplevel->new (
2142 root 1.2 title => "Minimap",
2143 root 1.1 name => "mapmap",
2144     x => 0,
2145     y => $FONTSIZE + 8,
2146     border_bg => [1, 1, 1, 192/255],
2147     bg => [1, 1, 1, 0],
2148     child => ($MAPMAP = new CFPlus::MapWidget::MapMap
2149     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2150     ),
2151     )->show;
2152    
2153     $MAPWIDGET = new CFPlus::MapWidget;
2154     $MAPWIDGET->connect (activate_console => sub {
2155     my ($mapwidget, $preset) = @_;
2156    
2157     $MESSAGE_WINDOW->activate_console ($preset)
2158     if $MESSAGE_WINDOW;
2159     });
2160     $MAPWIDGET->show;
2161     $MAPWIDGET->grab_focus;
2162    
2163     $COMPLETER = new CFPlus::MapWidget::Command::
2164     command => { },
2165     tooltip => "#completer_help",
2166     ;
2167    
2168     $SETUP_DIALOG = new CFPlus::UI::Toplevel
2169     title => "Setup",
2170     name => "setup_dialog",
2171     x => 'center',
2172     y => 'center',
2173     z => 2,
2174     force_w => $::WIDTH * 0.6,
2175     force_h => $::HEIGHT * 0.6,
2176     has_close_button => 1,
2177     ;
2178    
2179     $METASERVER = metaserver_dialog;
2180     $MESSAGE_WINDOW = new CFPlus::UI::MessageWindow;
2181    
2182     $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
2183     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2184    
2185     $SETUP_NOTEBOOK->add_tab (Login => $SETUP_LOGIN = login_setup,
2186     "Configure the server to play on, your username and password.");
2187     $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2188     "Configure other server related options.");
2189     $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2190     "Configure various client-specific settings.");
2191     $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2192     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2193     $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2194     "Configure the use of audio, sound effects and background music.");
2195     $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2196     "Lets you define, edit and delete key bindings."
2197     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2198     . "with nothing set and the recording started. After doing the actions you "
2199     . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2200     . "After pressing the combo the binding will be saved automatically and the "
2201     . "binding editor closes");
2202     $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2203     "Some debuggin' options. Do not ask.");
2204    
2205     $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2206    
2207     $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2208     tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2209    
2210     $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2211     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2212    
2213     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
2214    
2215     $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
2216     tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2217    
2218     $BUTTONBAR->add (new CFPlus::UI::Button
2219     text => "Save Config",
2220     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2221     on_activate => sub {
2222     $::CFG->{layout} = CFPlus::UI::get_layout;
2223     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
2224     status "Configuration Saved";
2225     0
2226     },
2227     );
2228    
2229     $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2230     tooltip => "View Documentation");
2231    
2232    
2233     $BUTTONBAR->add (new CFPlus::UI::Button
2234     text => "Quit",
2235     tooltip => "Terminates the program",
2236     on_activate => sub {
2237     if ($CONN) {
2238     open_quit_dialog;
2239     } else {
2240 root 1.4 EV::unloop EV::UNLOOP_ALL;
2241 root 1.1 }
2242     0
2243     },
2244     );
2245    
2246     $BUTTONBAR->show;
2247     $SETUP_DIALOG->show;
2248     $MESSAGE_WINDOW->show;
2249     }
2250    
2251     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2252     }
2253    
2254     sub video_shutdown {
2255     CFPlus::OpenGL::shutdown;
2256    
2257     undef $SDL_ACTIVE;
2258     }
2259    
2260     my %animate_object;
2261     my $animate_timer;
2262    
2263     my $fps = 9;
2264    
2265     sub force_refresh {
2266 root 1.4 $WANT_REFRESH->stop;
2267    
2268 root 1.1 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     }
2277    
2278 root 1.4 $WANT_REFRESH = EV::idle_ns \&force_refresh;
2279 root 1.1
2280 root 1.4 my $input = EV::timer 0, 1/60, sub {
2281 root 1.1 $NOW = time;
2282    
2283     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2284     for CFPlus::poll_events;
2285    
2286     if (%animate_object) {
2287     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2288     $WANT_REFRESH->start;
2289     }
2290 root 1.4 };
2291 root 1.1
2292     sub animation_start {
2293     my ($widget) = @_;
2294     $animate_object{$widget} = $widget;
2295     }
2296    
2297     sub animation_stop {
2298     my ($widget) = @_;
2299     delete $animate_object{$widget};
2300     }
2301    
2302     %SDL_CB = (
2303     CFPlus::SDL_QUIT => sub {
2304 root 1.4 EV::unloop EV::UNLOOP_ALL;
2305 root 1.1 },
2306     CFPlus::SDL_VIDEORESIZE => sub {
2307     },
2308     CFPlus::SDL_VIDEOEXPOSE => sub {
2309     CFPlus::UI::full_refresh;
2310     },
2311     CFPlus::SDL_ACTIVEEVENT => sub {
2312     # not useful, as APPACTIVE include sonly iconified state, not unmapped
2313     # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2314     # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2315     # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2316     # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2317     # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2318     },
2319     CFPlus::SDL_KEYDOWN => sub {
2320     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2321     # alt-enter
2322     $FULLSCREEN_ENABLE->toggle;
2323     video_shutdown;
2324     video_init;
2325     } else {
2326     CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2327     }
2328     },
2329     CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2330     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2331     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2332     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2333     CFPlus::SDL_USEREVENT => sub {
2334     if ($_[0]{code} == 1) {
2335     audio_channel_finished $_[0]{data1};
2336     } elsif ($_[0]{code} == 0) {
2337     audio_music_finished;
2338     }
2339     },
2340     );
2341    
2342     #############################################################################
2343    
2344     $SIG{INT} = $SIG{TERM} = sub { exit };
2345    
2346     {
2347     CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2348     CFPlus::DB::Server::run;
2349    
2350     CFPlus::UI::set_layout ($::CFG->{layout});
2351    
2352     my %DEF_CFG = (
2353     sdl_mode => 0,
2354 root 1.3 fullscreen => 1,
2355 root 1.1 fast => 0,
2356     force_opengl11 => undef,
2357     texture_compression => 1,
2358     map_scale => 1,
2359     fow_enable => 1,
2360     fow_intensity => 0,
2361     map_smoothing => 1,
2362     gui_fontsize => 1,
2363     log_fontsize => 0.7,
2364     gauge_fontsize => 1,
2365     gauge_size => 0.35,
2366     stat_fontsize => 0.7,
2367     mapsize => 100,
2368     audio_enable => 1,
2369     audio_hw_channels => 0,
2370     audio_hw_frequency => 0,
2371     audio_hw_chunksize => 0,
2372     audio_mix_channels => 8,
2373     effects_enable => 1,
2374     effects_volume => 1,
2375     bgm_enable => 1,
2376     bgm_volume => 0.5,
2377     output_rate => "",
2378     pickup => 0,
2379     inv_sort => "mtime",
2380     default => "profile", # default profile
2381     show_tips => 1,
2382     logview_max_par => 1000,
2383     );
2384    
2385     while (my ($k, $v) = each %DEF_CFG) {
2386     $CFG->{$k} = $v unless exists $CFG->{$k};
2387     }
2388    
2389     $CFG->{profile}{default}{host} ||= "gameserver.deliantra.net";
2390     $PROFILE = $CFG->{profile}{default};
2391    
2392     # convert old bindings (only default profile matters)
2393     if (my $bindings = delete $PROFILE->{bindings}) {
2394     while (my ($mod, $syms) = each %$bindings) {
2395     while (my ($sym, $cmds) = each %$syms) {
2396     push @{ $PROFILE->{macro} }, {
2397     accelkey => [$mod*1, $sym*1],
2398     action => $cmds,
2399     };
2400     }
2401     }
2402     }
2403    
2404     sdl_init;
2405    
2406     @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2407     @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2408     @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2409    
2410     @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2411    
2412     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2413    
2414     {
2415     my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2416     DejaVuSans.ttf
2417     DejaVuSansMono.ttf
2418     DejaVuSans-Bold.ttf
2419     DejaVuSansMono-Bold.ttf
2420     DejaVuSans-Oblique.ttf
2421     DejaVuSansMono-Oblique.ttf
2422     DejaVuSans-BoldOblique.ttf
2423     DejaVuSansMono-BoldOblique.ttf
2424     );
2425    
2426     CFPlus::add_font $_ for @fonts;
2427    
2428     CFPlus::pango_init;
2429    
2430     $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2431     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2432    
2433     $FONT_PROP->make_default;
2434     }
2435    
2436     # compare mono (ft) vs. rgba (cairo)
2437     # ft - 1.8s, cairo 3s, even in alpha-only mode
2438     # for my $rgba (0..1) {
2439     # my $t1 = Time::HiRes::time;
2440     # for (1..1000) {
2441     # my $layout = CFPlus::Layout->new ($rgba);
2442     # $layout->set_text ("hallo" x 100);
2443     # $layout->render;
2444     # }
2445     # my $t2 = Time::HiRes::time;
2446     # warn $t2-$t1;
2447     # }
2448    
2449     video_init;
2450     audio_init;
2451     }
2452    
2453     show_tip_of_the_day if $CFG->{show_tips};
2454    
2455 root 1.4 our $STARTUP_CANCEL = EV::idle sub {
2456     undef $::STARTUP_CANCEL;
2457 root 1.1 $startup_done->();
2458 root 1.4 };
2459 root 1.1
2460 root 1.4 EV::loop;
2461 root 1.1
2462     #video_shutdown;
2463     #audio_shutdown;
2464     CFPlus::SDL_Quit;
2465     CFPlus::DB::Server::stop;
2466    
2467     =head1 NAME
2468    
2469     deliantra - A Deliantra MORPG game client
2470    
2471     =head1 SYNOPSIS
2472    
2473     Just run it - no commandline arguments are supported.
2474    
2475     =head1 USAGE
2476    
2477     deliantra utilises OpenGL for all UI elements and the game. It is supposed to
2478     be used in fullscreen mode and interactively.
2479    
2480     =head1 DEBUGGING
2481    
2482    
2483     CFPLUS_DEBUG - environment variable
2484    
2485     1 draw borders around widgets
2486     2 add low-level widget info to tooltips
2487     4 show fps
2488     8 suppress tooltips
2489    
2490     =head1 AUTHOR
2491    
2492     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2493    
2494    
2495