ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.200
Committed: Thu Aug 2 14:35:21 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.199: +43 -4 lines
Log Message:
start a media page

File Contents

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