ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.228
Committed: Mon Oct 8 16:10:14 2007 UTC (16 years, 7 months ago) by root
Branch: MAIN
Changes since 1.227: +116 -15 lines
Log Message:
- create audio settings for frequency, channels, mixer voices and chunksize
- tune audio defaults ot be much less demanding

File Contents

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