ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.230
Committed: Mon Oct 8 17:05:29 2007 UTC (16 years, 7 months ago) by root
Branch: MAIN
Changes since 1.229: +10 -8 lines
Log Message:
*** empty log message ***

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 root 1.230 [0, "default", "Use System Default"],
909 root 1.228 map {
910 root 1.230 my $ms = sprintf "%dms", 1000 * $_ / ($CFG->{audio_hw_frequency} || 22050);
911 root 1.228 [$_, $ms, "$ms ($_ samples)"],
912     } 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
913     ]);
914    
915     my $text = !$freq
916     ? "audio is off"
917 root 1.230 : "audio is enabled\n"
918     . "frequency (Hz): $freq\n"
919     . "channels: $chans";
920 root 1.228
921     $AUDIO_INFO->set_text ($text);
922     }
923    
924 root 1.49 sub audio_setup {
925 root 1.104 my $vbox = new CFPlus::UI::VBox;
926 root 1.49
927 root 1.199 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1]);
928 root 1.49
929     my $row = 0;
930    
931 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable");
932     $table->add_at (1, $row++, new CFPlus::UI::CheckBox
933 root 1.1 state => $CFG->{audio_enable},
934     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.",
935 root 1.228 on_changed => sub { $CFG->{audio_enable} = $_[1]; 1 }
936 root 1.1 );
937 root 1.199
938 root 1.205 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Sound Effects");
939 root 1.199 $table->add_at (1, $row, new CFPlus::UI::CheckBox
940     expand => 1, state => $CFG->{effects_enable},
941     tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
942 root 1.205 on_changed => sub {
943     $CFG->{effects_enable} = $_[1];
944     $CONN->update_fx_want if $CONN;
945 root 1.228 1
946 root 1.205 }
947 root 1.199 );
948     $table->add_at (2, $row++, new CFPlus::UI::Slider
949     expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
950     tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
951 root 1.205 . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
952 root 1.228 on_changed => sub { $CFG->{effects_volume} = $_[1]; 1 }
953 root 1.199 );
954    
955 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music");
956 root 1.199 $table->add_at (1, $row, new CFPlus::UI::CheckBox
957 root 1.1 expand => 1, state => $CFG->{bgm_enable},
958     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
959 root 1.205 on_changed => sub {
960     $CFG->{bgm_enable} = $_[1];
961     $CONN->update_fx_want if $CONN;
962 root 1.219 audio_music_push;
963 root 1.228 1
964 root 1.205 }
965 root 1.1 );
966 root 1.199 $table->add_at (2, $row++, new CFPlus::UI::Slider
967 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
968     tooltip => "The volume of the background music. Changes are instant.",
969 root 1.195 on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
970 root 1.1 );
971    
972 root 1.228 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Frequency");
973     $table->add_at (1, $row++, new CFPlus::UI::Selector
974     c_colspan => 2, expand => 1,
975     value => $CFG->{audio_hw_frequency},
976     options => [
977 root 1.230 [ 0, "default" , "Use System Default"],
978 root 1.228 [11025, "11 kHz" , "11kHz (low quality)"],
979     [22050, "22 kHz" , "22kHz (reduced quality)"],
980     [44100, "44.1 kHz", "44.1kHz (cd quality)"],
981     [48000, "48 kHz" , "48kHz (studio quality)"],
982     ],
983     tooltip => "The sampling frequency to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
984     on_changed => sub {
985     $CFG->{audio_hw_frequency} = $_[1];
986     audio_tab_update;
987     1
988     }
989     );
990    
991     $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Channels");
992     $table->add_at (1, $row++, new CFPlus::UI::Selector
993     c_colspan => 2, expand => 1,
994     value => $CFG->{audio_hw_channels},
995     options => [
996 root 1.230 [0, "default" , "Use System Default"],
997 root 1.228 [1, "Mono" , "Mono (single channel, low quality)"],
998     [2, "Stereo" , "Stereo (dual channe, standard quality)"],
999     [4, "4 Ch Surround", "4 Channel Surround Sound (3d sound, high quality)"],
1000     [6, "6 Ch Surround", "6 Channel Surround Sound (3d sound + center + lfe)"],
1001     ],
1002     tooltip => "The number of independent sound channels to use. Higher sounds better, but also more cpu-intensive and might cause stuttering.",
1003     on_changed => sub {
1004     $CFG->{audio_hw_channels} = $_[1];
1005     audio_tab_update;
1006     1
1007     }
1008     );
1009    
1010     $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Latency");
1011     $table->add_at (1, $row++, $AUDIO_HW_CHUNKSIZE = new CFPlus::UI::Selector
1012     c_colspan => 2, expand => 1,
1013     value => $CFG->{audio_hw_chunksize},
1014     tooltip => "The guarenteed latency. Lower is better, but also more cpu-intensive and might cause stuttering. If music playback "
1015     . "is stuttering, increase this value. Values of 50-100ms are optimal.",
1016     on_changed => sub {
1017 root 1.230 $CFG->{audio_hw_chunksize} = $_[1];
1018 root 1.228 audio_tab_update;
1019     1
1020     }
1021     );
1022    
1023     # should really be a slider
1024     $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Mixer Voices");
1025     $table->add_at (1, $row++, new CFPlus::UI::ValSlider
1026     c_colspan => 2, expand => 1,
1027     tooltip => "The number of simultaneous sound effects possible. Higher is better, but also more cpu-intensive and might cause stuttering.",
1028     range => [$::CFG->{audio_mix_channels}, 4, 32, 0, 1],
1029     template => ">= 99",
1030     on_changed => sub {
1031     my ($slider, $value) = @_;
1032    
1033     $CFG->{audio_mix_channels} = $value
1034     if $value;
1035     1;
1036     }
1037     );
1038    
1039 root 1.178 $table->add_at (1, $row++, new CFPlus::UI::Button
1040 root 1.199 c_colspan => 2, expand => 1, align => 0, text => "Apply",
1041 root 1.1 tooltip => "Apply the audio settings",
1042 root 1.18 on_activate => sub {
1043 root 1.1 audio_shutdown ();
1044     audio_init ();
1045 root 1.74 0
1046 root 1.1 }
1047     );
1048    
1049 root 1.228 $vbox->add (new CFPlus::UI::FancyFrame
1050     expand => 1,
1051     label => "Audio Info",
1052     child => ($AUDIO_INFO = new CFPlus::UI::Label ellipsise => 0),
1053     );
1054    
1055     audio_tab_update;
1056    
1057 root 1.49 $vbox
1058 root 1.1 }
1059    
1060     sub set_gauge_window_fontsize {
1061     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
1062     $_->set_fontsize ($::CFG->{gauge_fontsize});
1063     }
1064     }
1065    
1066     sub make_gauge_window {
1067     my $gh = int $HEIGHT * $CFG->{gauge_size};
1068    
1069 root 1.104 my $win = new CFPlus::UI::Frame (
1070 root 1.30 force_x => 0,
1071     force_y => "max",
1072     force_w => $WIDTH,
1073     force_h => $gh,
1074 root 1.1 );
1075    
1076 root 1.104 $win->add (my $hbox = new CFPlus::UI::HBox
1077 root 1.1 children => [
1078 root 1.104 (new CFPlus::UI::HBox expand => 1),
1079     (new CFPlus::UI::VBox children => [
1080     (new CFPlus::UI::Empty expand => 1),
1081     (new CFPlus::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFPlus::UI::Table)),
1082 root 1.1 ]),
1083 root 1.104 (my $vbox = new CFPlus::UI::VBox),
1084 root 1.1 ],
1085     );
1086    
1087 root 1.104 $vbox->add (new CFPlus::UI::HBox
1088 root 1.1 expand => 1,
1089     children => [
1090 root 1.104 (new CFPlus::UI::Empty expand => 1),
1091     (my $hb = new CFPlus::UI::HBox),
1092 root 1.1 ],
1093     );
1094    
1095 root 1.109 $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health");
1096     $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana");
1097     $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace");
1098     $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food");
1099    
1100 root 1.221 $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
1101     $vbox->add (my $prg = new CFPlus::UI::ExperienceProgress);
1102     $vbox->add (my $sklprg = new CFPlus::UI::ExperienceProgress);
1103     $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
1104 root 1.1
1105     $GAUGES = {
1106 root 1.221 exp => $exp, prg => $prg, sklprg => $sklprg,
1107     win => $win, range => $rng,
1108     hp => $hg, mana => $mg, grace => $gg, food => $fg,
1109 root 1.1 };
1110    
1111     &set_gauge_window_fontsize;
1112    
1113     $win
1114     }
1115    
1116 root 1.65 sub debug_setup {
1117 root 1.104 my $table = new CFPlus::UI::Table;
1118 root 1.65
1119 root 1.178 $table->add_at (0, 0, new CFPlus::UI::Label text => "Widget Borders");
1120     $table->add_at (1, 0, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1121     $table->add_at (0, 1, new CFPlus::UI::Label text => "Tooltip Widget Info");
1122     $table->add_at (1, 1, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1123     $table->add_at (0, 2, new CFPlus::UI::Label text => "Show FPS");
1124     $table->add_at (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1125     $table->add_at (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips");
1126     $table->add_at (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1127     $table->add_at (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } );
1128 root 1.65
1129 root 1.178 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
1130 root 1.65
1131 root 1.181 $table->add_at (7,7, my $t = new CFPlus::UI::Table expand => 0);
1132 root 1.199 $t->add_at (0,0, new CFPlus::UI::Label text => "a a a a", c_rowspan => 1, c_colspan => 2);
1133     $t->add_at (2,0, new CFPlus::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1);
1134     $t->add_at (1,2, new CFPlus::UI::Label text => "c c c c", c_rowspan => 1, c_colspan => 2);
1135     $t->add_at (0,1, new CFPlus::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1);
1136 root 1.179 $t->add_at (1,1, new CFPlus::UI::Label text => "e");
1137    
1138 root 1.181 $table->add_at (7, 6, my $c = new CFPlus::UI::Canvas);
1139    
1140     $c->add_items ({
1141     type => "line_loop",
1142     color => [0, 1, 0],
1143     width => 9,
1144     coord_mode => "abs",
1145     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1146     });
1147    
1148     $c->add_items ({
1149     type => "lines",
1150     color => [1, 1, 0],
1151     width => 2,
1152     coord_mode => "rel",
1153     coord => [[0,0], [1,1], [1,0], [0,1]],
1154     });
1155    
1156     $c->add_items ({
1157     type => "polygon",
1158     color => [0, 0.43, 0],
1159     width => 2,
1160     coord_mode => "rel",
1161     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1162     });
1163    
1164 root 1.65 $table
1165     }
1166 elmex 1.24
1167 root 1.60 sub stats_window {
1168 root 1.104 my $r = new CFPlus::UI::ScrolledWindow (
1169 root 1.98 expand => 1,
1170     scroll_y => 1
1171     );
1172 root 1.104 $r->add (my $vb = new CFPlus::UI::VBox);
1173 root 1.1
1174 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1175     label => "Player",
1176     child => (my $pi = new CFPlus::UI::VBox),
1177     );
1178    
1179     $pi->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
1180 root 1.1 can_hover => 1, can_events => 1,
1181     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1182 root 1.114 $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
1183 root 1.1 can_hover => 1, can_events => 1,
1184     tooltip => "The map you are currently on (if supported by the server).");
1185    
1186 root 1.114 $pi->add (my $hb0 = new CFPlus::UI::HBox);
1187 root 1.104 $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
1188 elmex 1.5 can_hover => 1, can_events => 1,
1189 root 1.15 tooltip => "The weight of the player including all inventory items.");
1190 root 1.104 $hb0->add ($STATWIDS->{m_weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
1191 elmex 1.5 can_hover => 1, can_events => 1,
1192 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
1193 elmex 1.5
1194 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1195     label => "Primary/Secondary Statistics",
1196     child => (my $hb = new CFPlus::UI::HBox expand => 1),
1197     );
1198 root 1.104 $hb->add (my $tbl = new CFPlus::UI::Table expand => 1);
1199 root 1.1
1200     my $color2 = [1, 1, 0];
1201    
1202     for (
1203 root 1.98 [0, 0, st_str => "Str", 30],
1204     [0, 1, st_dex => "Dex", 30],
1205     [0, 2, st_con => "Con", 30],
1206     [0, 3, st_int => "Int", 30],
1207     [0, 4, st_wis => "Wis", 30],
1208     [0, 5, st_pow => "Pow", 30],
1209     [0, 6, st_cha => "Cha", 30],
1210    
1211     [2, 0, st_wc => "Wc", -120],
1212     [2, 1, st_ac => "Ac", -120],
1213     [2, 2, st_dam => "Dam", 120],
1214     [2, 3, st_arm => "Arm", 120],
1215     [2, 4, st_spd => "Spd", 10.54],
1216     [2, 5, st_wspd => "WSp", 10.54],
1217 root 1.1 ) {
1218 root 1.98 my ($col, $row, $id, $label, $template) = @$_;
1219 root 1.1
1220 root 1.178 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label
1221 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0,
1222 root 1.107 align => +1, template => $template, tooltip => "#stat_$label");
1223 root 1.178 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label
1224 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0,
1225 root 1.107 align => -1, text => $label, tooltip => "#stat_$label");
1226 root 1.1 }
1227    
1228 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1229     label => "Resistancies",
1230     child => (my $tbl2 = new CFPlus::UI::Table expand => 1),
1231     );
1232 root 1.1
1233     my $row = 0;
1234     my $col = 0;
1235    
1236     my %resist_names = (
1237 elmex 1.92 slow => ["Slow",
1238     "<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.)"],
1239     holyw => ["Holy Word",
1240     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1241     conf => ["Confusion",
1242     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1243     fire => ["Fire",
1244     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1245     depl => ["Depletion",
1246     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1247     magic => ["Magic",
1248     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1249     drain => ["Draining",
1250     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1251     acid => ["Acid",
1252     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1253     pois => ["Poison",
1254     "<b>Poison</b> (resistance to getting poisoned)"],
1255     para => ["Paralysation",
1256     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1257     deat => ["Death",
1258     "<b>Death</b> (resistance against death spells)"],
1259     phys => ["Physical",
1260     "<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.)"],
1261     blind => ["Blind",
1262     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1263     fear => ["Fear",
1264     "<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)"],
1265     tund => ["Turn undead",
1266     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1267     elec => ["Electricity",
1268     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1269     cold => ["Cold",
1270     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1271     ghit => ["Ghost hit",
1272     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1273 root 1.1 );
1274 root 1.178
1275 root 1.1 for (qw/slow holyw conf fire depl magic
1276     drain acid pois para deat phys
1277     blind fear tund elec cold ghit/)
1278     {
1279 root 1.178 $tbl2->add_at ($col, $row,
1280 root 1.1 $STATWIDS->{"res_$_"} =
1281 root 1.104 new CFPlus::UI::Label
1282 root 1.1 font => $FONT_FIXED,
1283     template => "-100%",
1284     align => +1,
1285     valign => 0,
1286     can_events => 1,
1287     can_hover => 1,
1288 elmex 1.92 tooltip => $resist_names{$_}->[1],
1289 root 1.1 );
1290 root 1.178 $tbl2->add_at ($col + 1, $row, new CFPlus::UI::Image
1291 root 1.1 font => $FONT_FIXED,
1292     can_hover => 1,
1293     can_events => 1,
1294 root 1.78 path => "ui/resist/resist_$_.png",
1295 elmex 1.92 tooltip => $resist_names{$_}->[1],
1296     );
1297 root 1.178 $tbl2->add_at ($col + 2, $row, new CFPlus::UI::Label
1298 elmex 1.92 text => $resist_names{$_}->[0],
1299     font => $FONT_FIXED,
1300     can_hover => 1,
1301     can_events => 1,
1302     tooltip => $resist_names{$_}->[1],
1303 root 1.1 );
1304    
1305     $row++;
1306     if ($row % 6 == 0) {
1307 elmex 1.92 $col += 3;
1308 root 1.1 $row = 0;
1309     }
1310     }
1311    
1312 root 1.95 #update_stats_window ({});
1313 root 1.1
1314 elmex 1.97 $r
1315 root 1.1 }
1316    
1317 elmex 1.92 sub skill_window {
1318 root 1.104 my $sw = new CFPlus::UI::ScrolledWindow (expand => 1);
1319 root 1.220
1320     $sw->add ($STATWIDS->{skill_tbl} = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1321    
1322 elmex 1.97 $sw
1323 elmex 1.92 }
1324    
1325 root 1.48 sub formsep($) {
1326     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1327 root 1.1 }
1328    
1329     my $METASERVER_ATIME;
1330    
1331     sub update_metaserver {
1332 elmex 1.81 my ($metaserver_dialog) = @_;
1333    
1334     $METASERVER = $metaserver_dialog
1335     if defined $metaserver_dialog;
1336    
1337 root 1.1 return if $METASERVER_ATIME > time;
1338     $METASERVER_ATIME = time + 60;
1339    
1340     my $table = $METASERVER->{table};
1341     $table->clear;
1342 root 1.178 $table->add_at (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1343 root 1.1
1344 root 1.125 my $ok = 0;
1345 root 1.1
1346 root 1.125 CFPlus::background {
1347     my $ua = CFPlus::lwp_useragent;
1348 root 1.1
1349 root 1.125 CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content;
1350     } sub {
1351     my ($msg) = @_;
1352     if ($msg) {
1353 root 1.1 $table->clear;
1354    
1355 root 1.62 my @tip = (
1356     "The current number of users logged in on the server.",
1357     "The hostname of the server.",
1358     "The time this server has been running without being restarted.",
1359     "The server software version - a '+' indicates a Crossfire+ server.",
1360     "Short information about this server provided by its admins.",
1361     );
1362     my @col = qw(#Users Host Uptime Version Description);
1363 root 1.178 $table->add_at ($_, 0, new CFPlus::UI::Label
1364 root 1.62 can_hover => 1, can_events => 1,
1365     align => 0, fg => [1, 1, 0],
1366     text => $col[$_], tooltip => $tip[$_])
1367     for 0 .. $#col;
1368 root 1.1
1369     my @align = qw(1 0 1 1 -1);
1370    
1371     my $y = 0;
1372 root 1.142 for my $m (@{ $msg->{servers} }) {
1373     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1374     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1375 root 1.1
1376     for ($desc) {
1377     s/<br>/\n/gi;
1378     s/<li>/\n· /gi;
1379     s/<.*?>//sgi;
1380 root 1.125 s/&amp;/&/g;
1381     s/&lt;/</g;
1382     s/&gt;/>/g;
1383 root 1.1 }
1384    
1385     $uptime = sprintf "%dd %02d:%02d:%02d",
1386 root 1.125 (int $uptime / 86400),
1387     (int $uptime / 3600) % 24,
1388     (int $uptime / 60) % 60,
1389     $uptime % 60;
1390 root 1.1
1391     $m = [$users, $host, $uptime, $version, $desc];
1392    
1393     $y++;
1394    
1395 root 1.178 $table->add_at (scalar @$m, $y, new CFPlus::UI::VBox children => [
1396 root 1.104 (new CFPlus::UI::Button
1397 root 1.62 text => "Use",
1398     tooltip => "Put this server into the <b>Host:Port</b> field",
1399     on_activate => sub {
1400 root 1.75 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1401 root 1.62 $METASERVER->hide;
1402 root 1.74 0
1403 root 1.62 },
1404     ),
1405 root 1.104 (new CFPlus::UI::Empty expand => 1),
1406 root 1.1 ]);
1407    
1408 root 1.178 $table->add_at ($_, $y, new CFPlus::UI::Label
1409 root 1.125 max_w => $::WIDTH * 0.4,
1410 root 1.62 ellipsise => 0,
1411     align => $align[$_],
1412     text => $m->[$_],
1413     tooltip => $tip[$_],
1414 root 1.142 fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1415 root 1.62 can_hover => 1,
1416     can_events => 1,
1417     fontsize => 0.8)
1418 root 1.1 for 0 .. $#$m;
1419     }
1420 root 1.125 } else {
1421     $ok or $label->set_text ("error while contacting metaserver");
1422 root 1.1 }
1423 root 1.125 };
1424    
1425 root 1.1 }
1426    
1427 root 1.40 sub metaserver_dialog {
1428 root 1.104 my $vbox = new CFPlus::UI::VBox;
1429     my $table = new CFPlus::UI::Table;
1430     $vbox->add (new CFPlus::UI::ScrolledWindow expand => 1, child => $table);
1431 elmex 1.81
1432 root 1.114 my $dialog = new CFPlus::UI::Toplevel
1433 root 1.62 title => "Server List",
1434     name => 'metaserver_dialog',
1435     x => 'center',
1436     y => 'center',
1437     z => 3,
1438 root 1.125 force_w => $::WIDTH * 0.9,
1439     force_h => $::HEIGHT * 0.7,
1440 elmex 1.81 child => $vbox,
1441 root 1.80 has_close_button => 1,
1442 elmex 1.81 table => $table,
1443 root 1.40 on_visibility_change => sub {
1444 elmex 1.81 update_metaserver ($_[0]) if $_[1];
1445 root 1.74 0
1446 root 1.40 },
1447     ;
1448    
1449     $dialog
1450     }
1451    
1452 root 1.1 sub server_setup {
1453 root 1.104 my $vbox = new CFPlus::UI::VBox;
1454 elmex 1.19
1455 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1456     label => "Connection Settings",
1457     child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]),
1458     );
1459 root 1.178 $table->add_at (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
1460 root 1.1
1461     {
1462 root 1.178 $table->add_at (1, 2, my $vbox = new CFPlus::UI::VBox);
1463 root 1.1
1464     $vbox->add (
1465 root 1.104 $HOST_ENTRY = new CFPlus::UI::Entry
1466 root 1.1 expand => 1,
1467 root 1.75 text => $CFG->{profile}{default}{host},
1468 root 1.1 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1469 root 1.18 on_changed => sub {
1470 root 1.1 my ($self, $value) = @_;
1471 root 1.75 $CFG->{profile}{default}{host} = $value;
1472 root 1.192 1
1473 root 1.1 }
1474     );
1475    
1476 root 1.104 $vbox->add (new CFPlus::UI::Button
1477 root 1.40 expand => 1,
1478     text => "Server List",
1479     other => $METASERVER,
1480 root 1.1 tooltip => "Show a list of available crossfire servers",
1481 root 1.74 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1482 root 1.192 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1483 root 1.1 );
1484     }
1485    
1486 root 1.178 $table->add_at (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
1487     $table->add_at (1, 4, new CFPlus::UI::Entry
1488 root 1.75 text => $CFG->{profile}{default}{user},
1489 root 1.1 tooltip => "The name of your character on the server",
1490 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1491 root 1.1 );
1492    
1493 root 1.178 $table->add_at (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
1494     $table->add_at (1, 5, new CFPlus::UI::Entry
1495 root 1.75 text => $CFG->{profile}{default}{password},
1496 root 1.1 hidden => 1,
1497     tooltip => "The password for your character",
1498 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1499 root 1.1 );
1500    
1501 root 1.178 $table->add_at (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1502     $table->add_at (1, 7, new CFPlus::UI::Slider
1503 root 1.30 force_w => 100,
1504 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1505     tooltip => "This is the size of the portion of the map update the server sends you. "
1506     . "If you set this to a high value you will be able to see further, "
1507     . "but you also increase bandwidth requirements and latency. "
1508     . "This option is only used once at log-in.",
1509 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1510 root 1.1 );
1511    
1512 root 1.189 $table->add_at (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
1513     $table->add_at (1, 8, new CFPlus::UI::Entry
1514 root 1.143 text => $CFG->{output_rate},
1515 root 1.192 tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1516     . "when sending data. When 0 or unset, the server "
1517     . "default will be used, which is usually around 100kb/s. Most servers will "
1518     . "dynamically find an optimal rate, so adjust this only when necessary.",
1519     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1520 root 1.143 );
1521    
1522 root 1.189 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1523     $table->add_at (1, 9, new CFPlus::UI::Entry
1524 root 1.1 text => $CFG->{output_count},
1525     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1526 root 1.192 on_changed => sub { $CFG->{output_count} = $_[1]; 1 },
1527 root 1.1 );
1528    
1529 root 1.189 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1530     $table->add_at (1, 10, new CFPlus::UI::Entry
1531 root 1.1 text => $CFG->{output_sync},
1532     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1533 root 1.192 on_changed => sub { $CFG->{output_sync} = $_[1]; 1 },
1534 root 1.1 );
1535    
1536 root 1.189 $table->add_at (1, 11, $LOGIN_BUTTON = new CFPlus::UI::Button
1537 root 1.1 expand => 1,
1538     align => 0,
1539     text => "Login",
1540 root 1.18 on_activate => sub {
1541 root 1.1 $CONN ? stop_game
1542     : start_game;
1543 root 1.192 1
1544 root 1.1 },
1545     );
1546    
1547 root 1.149 $vbox->add (new CFPlus::UI::FancyFrame
1548     label => "Server Info",
1549     child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1550     );
1551    
1552     $vbox
1553     }
1554    
1555     sub client_setup {
1556     my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
1557    
1558     my $row = 0;
1559    
1560 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1561     $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1562 root 1.119 state => $CFG->{show_tips},
1563     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1564     on_changed => sub {
1565     my ($self, $value) = @_;
1566 root 1.121 $CFG->{show_tips} = $value;
1567 root 1.119 0
1568     }
1569     );
1570    
1571 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1572     $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
1573 root 1.149 text => $CFG->{logview_max_par},
1574     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1575     . "sends more messages than this number, older messages get removed to save memory and "
1576     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1577     on_changed => sub {
1578     my ($self, $value) = @_;
1579 elmex 1.182 $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1580 root 1.149 0
1581     },
1582 root 1.67 );
1583    
1584 root 1.149 $table
1585 root 1.1 }
1586    
1587 root 1.49 sub autopickup_setup {
1588 root 1.170 my $r = new CFPlus::UI::ScrolledWindow (
1589     expand => 1,
1590     scroll_y => 1
1591     );
1592     $r->add (my $table = new CFPlus::UI::Table
1593     row_expand => [0],
1594     col_expand => [0, 1, 0, 1],
1595     );
1596 elmex 1.44
1597 elmex 1.43 for (
1598 root 1.51 ["General", 0, 0,
1599 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1600 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1601     ["Stop before pickup" => PICKUP_STOP],
1602     ["Debug autopickup" => PICKUP_DEBUG],
1603 root 1.51 ],
1604     ["Weapons", 0, 6,
1605 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1606     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1607     ["Bows" => PICKUP_BOW],
1608     ["Arrows" => PICKUP_ARROW],
1609 root 1.51 ],
1610     ["Armour", 0, 12,
1611 root 1.58 ["Helmets" => PICKUP_HELMET],
1612     ["Shields" => PICKUP_SHIELD],
1613     ["Body Armour" => PICKUP_ARMOUR],
1614     ["Boots" => PICKUP_BOOTS],
1615     ["Gloves" => PICKUP_GLOVES],
1616     ["Cloaks" => PICKUP_CLOAK],
1617 root 1.51 ],
1618    
1619 root 1.133 ["Readables", 2, 0,
1620 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1621     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1622     ["Normal Books/Scrolls" => PICKUP_READABLES],
1623 root 1.51 ],
1624 root 1.133 ["Misc", 2, 5,
1625 root 1.58 ["Food" => PICKUP_FOOD],
1626     ["Drinks" => PICKUP_DRINK],
1627     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1628     ["Keys" => PICKUP_KEY],
1629     ["Magical Items" => PICKUP_MAGICAL],
1630     ["Potions" => PICKUP_POTION],
1631     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1632     ["Ignore cursed" => PICKUP_NOT_CURSED],
1633     ["Jewelery" => PICKUP_JEWELS],
1634 root 1.133 ["Flesh" => PICKUP_FLESH],
1635 root 1.51 ],
1636 elmex 1.66 ["Weight/Value ratio", 2, 17]
1637 elmex 1.43 )
1638     {
1639 root 1.51 my ($title, $x, $y, @bits) = @$_;
1640 root 1.178 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1641 root 1.51
1642     for (@bits) {
1643     ++$y;
1644    
1645 elmex 1.43 my $mask = $_->[1];
1646 root 1.178 $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1647     $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1648 elmex 1.83 state => $::CFG->{pickup} & $mask,
1649 elmex 1.43 on_changed => sub {
1650     my ($box, $value) = @_;
1651 root 1.63
1652 elmex 1.43 if ($value) {
1653 elmex 1.45 $::CFG->{pickup} |= $mask;
1654 elmex 1.43 } else {
1655 root 1.63 $::CFG->{pickup} &= ~$mask;
1656 elmex 1.43 }
1657 root 1.63
1658     $::CONN->send_command ("pickup $::CFG->{pickup}")
1659 elmex 1.45 if defined $::CONN;
1660 root 1.74
1661     0
1662 elmex 1.43 });
1663 root 1.86
1664     ${$_->[2]} = $checkbox if $_->[2];
1665 elmex 1.43 }
1666     }
1667    
1668 root 1.178 $table->add_at (2, 18, new CFPlus::UI::ValSlider
1669 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1670     template => ">= 99",
1671 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1672     on_changed => sub {
1673     my ($slider, $value) = @_;
1674    
1675 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1676 elmex 1.66 $::CFG->{pickup} |= int $value
1677     if $value;
1678     1;
1679     });
1680 elmex 1.83
1681 root 1.178 $table->add_at (3, 18, new CFPlus::UI::Button
1682 elmex 1.66 text => "set",
1683     on_activate => sub {
1684     $::CONN->send_command ("pickup $::CFG->{pickup}")
1685     if defined $::CONN;
1686 root 1.74 0
1687 elmex 1.66 });
1688    
1689 root 1.170 $r
1690 elmex 1.43 }
1691    
1692 root 1.102 my %SORT_ORDER = (
1693     type => undef,
1694 root 1.130 mtime => sub {
1695     my $NOW = time;
1696     sort {
1697     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1698     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1699    
1700     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1701     or $btime <=> $atime
1702     or $a->{type} <=> $b->{type}
1703     } @_
1704     },
1705 root 1.102 weight => sub { sort {
1706     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1707     or $a->{type} <=> $b->{type}
1708     } @_ },
1709     );
1710    
1711 elmex 1.85 sub inventory_widget {
1712 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1713 root 1.1
1714 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1715     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1716 root 1.99
1717 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1718 root 1.99
1719     use sort 'stable';
1720    
1721 root 1.104 $hb1->add (new CFPlus::UI::Selector
1722 root 1.102 value => $::CFG->{inv_sort},
1723 root 1.99 options => [
1724 root 1.102 [type => "Type/Name"],
1725     [mtime => "Recent/Normal/Locked"],
1726     [weight => "Weight/Type"],
1727 root 1.99 ],
1728     on_changed => sub {
1729 root 1.102 $::CFG->{inv_sort} = $_[1];
1730     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1731 root 1.99 },
1732     );
1733 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1734 root 1.99 #TODO# update to weigh/maxweight
1735 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1736 root 1.99
1737 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1738     $sw1->add ($INV = new CFPlus::UI::Inventory);
1739 root 1.127 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1740 root 1.1
1741 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1742 elmex 1.17
1743 root 1.215 $vb2->add ($INVR_HB = new CFPlus::UI::HBox);
1744 elmex 1.14
1745 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1746     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1747 root 1.1
1748 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1749 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1750 elmex 1.27
1751 elmex 1.85 $hb
1752 root 1.1 }
1753    
1754 root 1.200 sub media_window {
1755     my $vb = new CFPlus::UI::VBox;
1756    
1757     $vb->add (new CFPlus::UI::FancyFrame
1758     label => "Currently playing music",
1759 root 1.201 child => new CFPlus::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1760     child => ($MUSIC_PLAYING_WIDGET = new CFPlus::UI::Label ellipsise => 0, fontsize => 0.8),
1761 root 1.200 );
1762    
1763     $vb->add (new CFPlus::UI::FancyFrame
1764 root 1.201 label => "Other media used in this session",
1765     expand => 1,
1766     child => ($LICENSE_WIDGET = new CFPlus::UI::TextScroller
1767 root 1.200 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1768     );
1769    
1770     $vb
1771     }
1772    
1773 root 1.201 sub add_license {
1774     my ($meta) = @_;
1775    
1776 root 1.219 $meta = $meta->{data}
1777 root 1.201 or return;
1778    
1779     $meta->{license} || $meta->{author} || $meta->{source}
1780     or return;
1781    
1782     $LICENSE_WIDGET->add_paragraph ({
1783     fg => [1, 1, 1, 1],
1784     markup => "<small>"
1785     . "<b>Name:</b> " . (CFPlus::asxml $meta->{name}) . "\n"
1786     . "<b>Author:</b> " . (CFPlus::asxml $meta->{author}) . "\n"
1787     . "<b>Source:</b> " . (CFPlus::asxml $meta->{source}) . "\n"
1788     . "<b>License:</b> " . (CFPlus::asxml $meta->{license}) . "\n"
1789     . "</small>",
1790     });
1791     $LICENSE_WIDGET->scroll_to_bottom;
1792     }
1793    
1794 root 1.86 sub toggle_player_page {
1795     my ($widget) = @_;
1796    
1797     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1798     $PL_WINDOW->hide;
1799     } else {
1800     $PL_NOTEBOOK->set_current_page ($widget);
1801     $PL_WINDOW->show;
1802     }
1803     }
1804    
1805 elmex 1.85 sub player_window {
1806 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1807 elmex 1.85 x => "center",
1808     y => "center",
1809     force_w => $WIDTH * 9/10,
1810     force_h => $HEIGHT * 9/10,
1811     title => "Player",
1812 elmex 1.90 name => "playerbook",
1813 elmex 1.85 has_close_button => 1
1814     ;
1815    
1816     my $ntb =
1817     $PL_NOTEBOOK =
1818 root 1.157 new CFPlus::UI::Notebook expand => 1;
1819 root 1.86
1820 root 1.184 $ntb->add_tab (
1821 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1822 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1823     );
1824 root 1.184 $ntb->add_tab (
1825 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1826 elmex 1.92 "Shows all your Skills."
1827 elmex 1.85 );
1828 elmex 1.97
1829 root 1.131 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1830     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1831 root 1.184 $ntb->add_tab (
1832 elmex 1.97 "Spellbook (F4)" => $spellsw,
1833 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1834 elmex 1.85 );
1835 root 1.184 $ntb->add_tab (
1836 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1837 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1838     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1839 elmex 1.85 );
1840 root 1.223 $ntb->add_tab (Pickup => $PICKUP_PAGE = autopickup_setup,
1841 root 1.155 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1842 elmex 1.85
1843 root 1.200 $ntb->add_tab (Media => media_window,
1844     "License, Author and Source info for media sent by the server.");
1845    
1846 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1847 root 1.86
1848 elmex 1.85 $plwin->add ($ntb);
1849     $plwin
1850 elmex 1.38 }
1851    
1852 root 1.49 sub keyboard_setup {
1853 root 1.137 CFPlus::Macro::keyboard_setup
1854 elmex 1.24 }
1855    
1856 root 1.64 sub help_window {
1857 root 1.114 my $win = new CFPlus::UI::Toplevel
1858 root 1.41 x => 'center',
1859     y => 'center',
1860 root 1.119 z => 4,
1861 root 1.41 name => 'doc_browser',
1862     force_w => int $WIDTH * 7/8,
1863     force_h => int $HEIGHT * 7/8,
1864 root 1.87 title => "Help Browser",
1865     has_close_button => 1;
1866 root 1.1
1867 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1868 root 1.1
1869 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1870     label => "Navigation",
1871     child => (my $buttons = new CFPlus::UI::HBox),
1872     );
1873 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1874 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1875 root 1.1
1876 root 1.111 my @history;
1877     my @future;
1878 root 1.112 my $curnode;
1879 root 1.111
1880     my $load_node; $load_node = sub {
1881 root 1.115 my ($node, $para) = @_;
1882 root 1.111
1883     $buttons->clear;
1884    
1885 root 1.116 $buttons->add (new CFPlus::UI::Button
1886     text => "⇤",
1887     tooltip => "back to the starting page",
1888     on_activate => sub {
1889     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1890     unshift @future, @history;
1891     @history = ();
1892     $load_node->(@{shift @future});
1893     },
1894     );
1895    
1896 root 1.111 if (@history) {
1897 root 1.112 $buttons->add (new CFPlus::UI::Button
1898 root 1.113 text => "⋘",
1899 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1900 root 1.112 on_activate => sub {
1901 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1902     $load_node->(@{pop @history});
1903 root 1.113 },
1904     );
1905 root 1.112 }
1906 root 1.111
1907 root 1.112 if (@future) {
1908     $buttons->add (new CFPlus::UI::Button
1909 root 1.113 text => "â‹™",
1910 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1911 root 1.112 on_activate => sub {
1912 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1913     $load_node->(@{shift @future});
1914 root 1.113 },
1915     );
1916 root 1.111 }
1917 root 1.112
1918 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1919    
1920     my @path = CFPlus::Pod::full_path_of $node;
1921     pop @path; # drop current node
1922    
1923     for my $node (@path) {
1924     $buttons->add (new CFPlus::UI::Button
1925     text => $node->{kw}[0],
1926     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1927     on_activate => sub {
1928 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1929 root 1.113 $load_node->($node);
1930     },
1931     );
1932     $buttons->add (new CFPlus::UI::Label text => "/");
1933     }
1934    
1935 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1936 root 1.113
1937 root 1.112 $curnode = $node;
1938    
1939 root 1.111 $viewer->clear;
1940 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1941 root 1.115 $viewer->scroll_to ($para);
1942 root 1.111 };
1943    
1944     $load_node->(CFPlus::Pod::find pod => "mainpage");
1945    
1946 root 1.113 $CFPlus::Pod::goto_document = sub {
1947 root 1.111 my (@path) = @_;
1948    
1949 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1950 root 1.111
1951 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1952 root 1.111 $win->show;
1953     };
1954 root 1.1
1955     $win
1956     }
1957    
1958 root 1.119 sub open_string_query {
1959     my ($title, $cb, $txt, $tooltip) = @_;
1960     my $dialog = new CFPlus::UI::Toplevel
1961     x => "center",
1962     y => "center",
1963     z => 50,
1964     force_w => $WIDTH * 4/5,
1965     title => $title;
1966    
1967     $dialog->add (
1968     my $e = new CFPlus::UI::Entry
1969     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1970     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1971     tooltip => $tooltip
1972     );
1973    
1974     $e->grab_focus;
1975     $e->set_text ($txt) if $txt;
1976     $dialog->show;
1977     }
1978    
1979     sub open_quit_dialog {
1980     unless ($QUIT_DIALOG) {
1981     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1982     x => "center",
1983     y => "center",
1984     z => 50,
1985     title => "Really Quit?",
1986     on_key_down => sub {
1987     my ($dialog, $ev) = @_;
1988     $ev->{sym} == 27 and $dialog->hide;
1989     }
1990     ;
1991    
1992     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1993    
1994     $vb->add (new CFPlus::UI::Label
1995     text => "You should find a savebed and apply it first!",
1996     max_w => $WIDTH * 0.25,
1997     ellipsize => 0,
1998     );
1999     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
2000     $hb->add (new CFPlus::UI::Button
2001     text => "Ok",
2002     expand => 1,
2003     on_activate => sub { $QUIT_DIALOG->hide; 0 },
2004     );
2005     $hb->add (new CFPlus::UI::Button
2006     text => "Quit anyway",
2007     expand => 1,
2008 root 1.213 on_activate => sub { Event::unloop_all },
2009 root 1.119 );
2010     }
2011    
2012     $QUIT_DIALOG->show;
2013     $QUIT_DIALOG->grab_focus;
2014     }
2015    
2016     sub show_tip_of_the_day {
2017     # find all tips
2018     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
2019    
2020 root 1.148 CFPlus::DB::get state => "tip_of_the_day", sub {
2021     my ($todindex) = @_;
2022     $todindex = 0 if $todindex >= @tod;
2023     CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
2024    
2025     # create dialog
2026     my $dialog;
2027    
2028     my $close = sub {
2029     $dialog->destroy;
2030     };
2031    
2032     $dialog = new CFPlus::UI::Toplevel
2033     x => "center",
2034     y => "center",
2035     z => 3,
2036     name => 'tip_of_the_day',
2037     force_w => int $WIDTH * 4/9,
2038     force_h => int $WIDTH * 2/9,
2039     title => "Tip of the day #" . (1 + $todindex),
2040     child => my $vbox = new CFPlus::UI::VBox,
2041     has_close_button => 1,
2042     on_delete => $close,
2043     ;
2044    
2045     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
2046     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
2047     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
2048    
2049     $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
2050    
2051 root 1.178 $table->add_at (0, 0, new CFPlus::UI::Button
2052 root 1.148 text => "Close",
2053     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>.",
2054     on_activate => $close,
2055     );
2056 root 1.119
2057 root 1.178 $table->add_at (2, 0, new CFPlus::UI::Button
2058 root 1.148 text => "Next",
2059     tooltip => "Show the next <b>Tip of the day</b>.",
2060     on_activate => sub {
2061     $close->();
2062     &show_tip_of_the_day;
2063     },
2064     );
2065 root 1.119
2066 root 1.148 $dialog->show;
2067 root 1.119 };
2068     }
2069    
2070 root 1.1 sub sdl_init {
2071 root 1.104 CFPlus::SDL_Init
2072 root 1.1 and die "SDL::Init failed!\n";
2073     }
2074    
2075     sub video_init {
2076     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
2077    
2078     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
2079    
2080 root 1.172 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
2081 root 1.1 $FULLSCREEN = $CFG->{fullscreen};
2082     $FAST = $CFG->{fast};
2083    
2084 root 1.172 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
2085 root 1.104 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
2086 root 1.1
2087     $SDL_ACTIVE = 1;
2088     $LAST_REFRESH = time - 0.01;
2089    
2090 root 1.104 CFPlus::OpenGL::init;
2091 root 1.223 CFPlus::Macro::init;
2092 root 1.1
2093     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
2094    
2095 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
2096 root 1.1
2097     #############################################################################
2098    
2099     if ($DEBUG_STATUS) {
2100 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
2101 root 1.1 } else {
2102 root 1.223 # create/configure the widgets
2103    
2104     $CFPlus::UI::ROOT->connect (key_down => sub {
2105     my (undef, $ev) = @_;
2106    
2107     if (my @macros = CFPlus::Macro::find $ev) {
2108     CFPlus::Macro::execute $_ for @macros;
2109    
2110     return 1;
2111     }
2112    
2113     0
2114     });
2115 root 1.1
2116 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
2117 root 1.30 padding => 0,
2118     z => 100,
2119     force_x => "max",
2120     force_y => 0;
2121 root 1.1 $DEBUG_STATUS->show;
2122 elmex 1.34
2123 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
2124 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
2125 root 1.1
2126 root 1.104 (new CFPlus::UI::Frame
2127 root 1.1 bg => [0, 0, 0, 0.4],
2128 root 1.30 force_x => 0,
2129     force_y => "max",
2130 root 1.1 child => $STATUSBOX,
2131     )->show;
2132    
2133 root 1.114 CFPlus::UI::Toplevel->new (
2134 root 1.47 title => "Map",
2135 root 1.42 name => "mapmap",
2136 root 1.30 x => 0,
2137     y => $FONTSIZE + 8,
2138 root 1.1 border_bg => [1, 1, 1, 192/255],
2139     bg => [1, 1, 1, 0],
2140 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
2141 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2142     ),
2143     )->show;
2144    
2145 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
2146 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2147     my ($mapwidget, $preset) = @_;
2148    
2149 elmex 1.182 $MESSAGE_WINDOW->activate_console ($preset)
2150     if $MESSAGE_WINDOW;
2151 root 1.1 });
2152     $MAPWIDGET->show;
2153 root 1.74 $MAPWIDGET->grab_focus;
2154 root 1.1
2155 root 1.223 $COMPLETER = new CFPlus::MapWidget::Command::
2156     command => { },
2157     tooltip => "#completer_help",
2158     ;
2159    
2160 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
2161 root 1.49 title => "Setup",
2162     name => "setup_dialog",
2163     x => 'center',
2164     y => 'center',
2165 root 1.53 z => 2,
2166 root 1.49 force_w => $::WIDTH * 0.6,
2167     force_h => $::HEIGHT * 0.6,
2168 root 1.74 has_close_button => 1,
2169 root 1.49 ;
2170    
2171 elmex 1.81 $METASERVER = metaserver_dialog;
2172 elmex 1.182 $MESSAGE_WINDOW = new CFPlus::UI::MessageWindow;
2173 elmex 1.81
2174 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
2175     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2176 root 1.49
2177 root 1.184 $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2178 root 1.49 "Configure the server to play on, your username, password and other server-related options.");
2179 root 1.184 $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2180 root 1.149 "Configure various client-specific settings.");
2181 root 1.184 $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2182 root 1.49 "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2183 root 1.184 $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2184 root 1.49 "Configure the use of audio, sound effects and background music.");
2185 root 1.184 $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2186 root 1.75 "Lets you define, edit and delete key bindings."
2187     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2188 root 1.49 . "with nothing set and the recording started. After doing the actions you "
2189 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2190 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
2191     . "binding editor closes");
2192 root 1.184 $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2193 root 1.75 "Some debuggin' options. Do not ask.");
2194 root 1.49
2195 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2196 root 1.1
2197 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2198 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2199    
2200 elmex 1.182 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2201 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2202    
2203     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
2204    
2205 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
2206 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2207 root 1.1
2208 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
2209 root 1.1 text => "Save Config",
2210     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2211 root 1.18 on_activate => sub {
2212 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
2213     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
2214 root 1.1 status "Configuration Saved";
2215 root 1.74 0
2216 root 1.1 },
2217     );
2218    
2219 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2220 root 1.1 tooltip => "View Documentation");
2221    
2222 elmex 1.134
2223 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
2224 root 1.18 text => "Quit",
2225     tooltip => "Terminates the program",
2226     on_activate => sub {
2227 root 1.1 if ($CONN) {
2228     open_quit_dialog;
2229     } else {
2230 root 1.213 Event::unloop_all;
2231 root 1.1 }
2232 root 1.74 0
2233 root 1.1 },
2234     );
2235    
2236     $BUTTONBAR->show;
2237 root 1.49 $SETUP_DIALOG->show;
2238 elmex 1.214 $MESSAGE_WINDOW->show;
2239 root 1.49 }
2240 root 1.1
2241 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2242 root 1.1 }
2243    
2244     sub video_shutdown {
2245 root 1.104 CFPlus::OpenGL::shutdown;
2246 root 1.73
2247 root 1.1 undef $SDL_ACTIVE;
2248     }
2249    
2250     my %animate_object;
2251     my $animate_timer;
2252    
2253     my $fps = 9;
2254    
2255     sub force_refresh {
2256 root 1.208 if ($ENV{CFPLUS_DEBUG} & 4) {
2257     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2258     debug sprintf "%3.2f", $fps;
2259     }
2260 root 1.1
2261 root 1.104 $CFPlus::UI::ROOT->draw;
2262 root 1.208 CFPlus::SDL_GL_SwapBuffers;
2263 root 1.1 $LAST_REFRESH = $NOW;
2264 root 1.208 $WANT_REFRESH->stop;
2265     }
2266 root 1.1
2267 root 1.208 $WANT_REFRESH = Event->idle (min => 0.001, max => 0.06, parked => 1, cb => \&force_refresh);
2268 root 1.1
2269 root 1.208 my $input = Event->timer (after => 0, hard => 0, interval => 1 / 50, cb => sub {
2270 root 1.1 $NOW = time;
2271    
2272     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2273 root 1.169 for CFPlus::poll_events;
2274 root 1.1
2275     if (%animate_object) {
2276     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2277 root 1.208 $WANT_REFRESH->start;
2278 root 1.1 }
2279     });
2280    
2281     sub animation_start {
2282     my ($widget) = @_;
2283     $animate_object{$widget} = $widget;
2284     }
2285    
2286     sub animation_stop {
2287     my ($widget) = @_;
2288     delete $animate_object{$widget};
2289     }
2290    
2291     %SDL_CB = (
2292 root 1.104 CFPlus::SDL_QUIT => sub {
2293 root 1.222 Event::unloop_all;
2294 root 1.1 },
2295 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2296 root 1.1 },
2297 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2298     CFPlus::UI::full_refresh;
2299 root 1.1 },
2300 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2301 root 1.137 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2302 root 1.171 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2303     # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2304 root 1.137 # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2305     # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2306     # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2307 root 1.1 },
2308 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2309     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2310 root 1.1 # alt-enter
2311 root 1.94 $FULLSCREEN_ENABLE->toggle;
2312 root 1.1 video_shutdown;
2313     video_init;
2314     } else {
2315 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2316 root 1.1 }
2317     },
2318 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2319     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2320     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2321     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2322     CFPlus::SDL_USEREVENT => sub {
2323 root 1.1 if ($_[0]{code} == 1) {
2324     audio_channel_finished $_[0]{data1};
2325     } elsif ($_[0]{code} == 0) {
2326     audio_music_finished;
2327     }
2328     },
2329     );
2330    
2331     #############################################################################
2332    
2333     $SIG{INT} = $SIG{TERM} = sub { exit };
2334    
2335     {
2336 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2337 root 1.147 CFPlus::DB::Server::run;
2338    
2339 root 1.104 CFPlus::UI::set_layout ($::CFG->{layout});
2340 root 1.1
2341     my %DEF_CFG = (
2342 root 1.216 sdl_mode => 0,
2343     fullscreen => 0,
2344     fast => 0,
2345 root 1.217 force_opengl11 => undef,
2346 root 1.216 texture_compression => 1,
2347     map_scale => 1,
2348     fow_enable => 1,
2349     fow_intensity => 0,
2350     map_smoothing => 1,
2351     gui_fontsize => 1,
2352     log_fontsize => 0.7,
2353     gauge_fontsize => 1,
2354     gauge_size => 0.35,
2355     stat_fontsize => 0.7,
2356     mapsize => 100,
2357     audio_enable => 1,
2358 root 1.230 audio_hw_channels => 0,
2359     audio_hw_frequency => 0,
2360 root 1.229 audio_hw_chunksize => 0,
2361 root 1.228 audio_mix_channels => 8,
2362 root 1.216 effects_enable => 1,
2363     effects_volume => 1,
2364     bgm_enable => 1,
2365     bgm_volume => 0.5,
2366     output_sync => 1,
2367     output_count => 1,
2368     output_rate => "",
2369     pickup => 0,
2370     inv_sort => "mtime",
2371     default => "profile", # default profile
2372     show_tips => 1,
2373     logview_max_par => 1000,
2374 root 1.1 );
2375 root 1.75
2376 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2377     $CFG->{$k} = $v unless exists $CFG->{$k};
2378     }
2379    
2380 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2381 root 1.137 $PROFILE = $CFG->{profile}{default};
2382    
2383     # convert old bindings (only default profile matters)
2384     if (my $bindings = delete $PROFILE->{bindings}) {
2385     while (my ($mod, $syms) = each %$bindings) {
2386     while (my ($sym, $cmds) = each %$syms) {
2387     push @{ $PROFILE->{macro} }, {
2388     accelkey => [$mod*1, $sym*1],
2389     action => $cmds,
2390     };
2391     }
2392     }
2393     }
2394 root 1.75
2395 root 1.1 sdl_init;
2396    
2397 root 1.172 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2398     @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2399 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2400 root 1.1
2401 root 1.177 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2402    
2403 root 1.1 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2404    
2405     {
2406 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2407 root 1.1 DejaVuSans.ttf
2408     DejaVuSansMono.ttf
2409     DejaVuSans-Bold.ttf
2410     DejaVuSansMono-Bold.ttf
2411     DejaVuSans-Oblique.ttf
2412     DejaVuSansMono-Oblique.ttf
2413     DejaVuSans-BoldOblique.ttf
2414     DejaVuSansMono-BoldOblique.ttf
2415     );
2416    
2417 root 1.104 CFPlus::add_font $_ for @fonts;
2418 root 1.1
2419 root 1.104 CFPlus::pango_init;
2420 root 1.1
2421 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2422     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2423 root 1.1
2424     $FONT_PROP->make_default;
2425     }
2426    
2427     # compare mono (ft) vs. rgba (cairo)
2428     # ft - 1.8s, cairo 3s, even in alpha-only mode
2429     # for my $rgba (0..1) {
2430     # my $t1 = Time::HiRes::time;
2431     # for (1..1000) {
2432 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2433 root 1.1 # $layout->set_text ("hallo" x 100);
2434     # $layout->render;
2435     # }
2436     # my $t2 = Time::HiRes::time;
2437     # warn $t2-$t1;
2438     # }
2439    
2440     video_init;
2441     audio_init;
2442     }
2443    
2444 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2445    
2446 root 1.222 Event->idle (cb => sub {
2447     $_[0]->w->cancel;
2448     $startup_done->();
2449     });
2450    
2451 root 1.1 Event::loop;
2452    
2453 root 1.213 #video_shutdown;
2454     #audio_shutdown;
2455     CFPlus::SDL_Quit;
2456     CFPlus::DB::Server::stop;
2457 root 1.1
2458     =head1 NAME
2459    
2460 root 1.204 cfplus - A Crossfire TRT and Crossfire game client
2461 root 1.1
2462     =head1 SYNOPSIS
2463    
2464     Just run it - no commandline arguments are supported.
2465    
2466     =head1 USAGE
2467    
2468 root 1.204 cfplus utilises OpenGL for all UI elements and the game. It is supposed to
2469     be used in fullscreen mode and interactively.
2470 root 1.1
2471 root 1.39 =head1 DEBUGGING
2472    
2473    
2474     CFPLUS_DEBUG - environment variable
2475    
2476     1 draw borders around widgets
2477     2 add low-level widget info to tooltips
2478     4 show fps
2479     8 suppress tooltips
2480    
2481 root 1.1 =head1 AUTHOR
2482    
2483     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2484    
2485    
2486