ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.199
Committed: Wed Aug 1 14:27:21 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.198: +31 -11 lines
Log Message:
UI tweaks, effects volume

File Contents

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