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