ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.206
Committed: Thu Aug 9 03:40:44 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.205: +3 -0 lines
Log Message:
do NOT load all audio chunks the server sends into memory automatically, especially as the server buggily sends way too many

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