ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.192
Committed: Mon Jul 30 02:16:16 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.191: +13 -12 lines
Log Message:
document new output-rate stuff

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