ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.191
Committed: Sun Jul 29 21:31:48 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.190: +61 -43 lines
Log Message:
finally, primitive server-supplied sound effects

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 0
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]; 0 },
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 }
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 }
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; 0 },
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 approximate bandwidth in bytes per second that the server should not exceed "
1120 . "when sending images, to ensure interactiveness. When 0 or unset, the server "
1121 . "default will be used, which is usually around 100kb/s.",
1122 on_changed => sub { $CFG->{output_rate} = $_[1]; 0 },
1123 );
1124
1125 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1126 $table->add_at (1, 9, new CFPlus::UI::Entry
1127 text => $CFG->{output_count},
1128 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1129 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
1130 );
1131
1132 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1133 $table->add_at (1, 10, new CFPlus::UI::Entry
1134 text => $CFG->{output_sync},
1135 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1136 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
1137 );
1138
1139 $table->add_at (1, 11, $LOGIN_BUTTON = new CFPlus::UI::Button
1140 expand => 1,
1141 align => 0,
1142 text => "Login",
1143 on_activate => sub {
1144 $CONN ? stop_game
1145 : start_game;
1146 0
1147 },
1148 );
1149
1150 $vbox->add (new CFPlus::UI::FancyFrame
1151 label => "Server Info",
1152 child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1153 );
1154
1155 $vbox
1156 }
1157
1158 sub client_setup {
1159 my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
1160
1161 my $row = 0;
1162
1163 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1164 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1165 state => $CFG->{show_tips},
1166 tooltip => "Show the <b>Tip of the day</b> window at startup?",
1167 on_changed => sub {
1168 my ($self, $value) = @_;
1169 $CFG->{show_tips} = $value;
1170 0
1171 }
1172 );
1173
1174 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1175 $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
1176 text => $CFG->{logview_max_par},
1177 tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1178 . "sends more messages than this number, older messages get removed to save memory and "
1179 . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1180 on_changed => sub {
1181 my ($self, $value) = @_;
1182 $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1183 0
1184 },
1185 );
1186
1187 $table
1188 }
1189
1190 sub autopickup_setup {
1191 my $r = new CFPlus::UI::ScrolledWindow (
1192 expand => 1,
1193 scroll_y => 1
1194 );
1195 $r->add (my $table = new CFPlus::UI::Table
1196 row_expand => [0],
1197 col_expand => [0, 1, 0, 1],
1198 );
1199
1200 for (
1201 ["General", 0, 0,
1202 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1203 ["Inhibit autopickup" => PICKUP_INHIBIT],
1204 ["Stop before pickup" => PICKUP_STOP],
1205 ["Debug autopickup" => PICKUP_DEBUG],
1206 ],
1207 ["Weapons", 0, 6,
1208 ["All weapons" => PICKUP_ALLWEAPON],
1209 ["Missile weapons" => PICKUP_MISSILEWEAPON],
1210 ["Bows" => PICKUP_BOW],
1211 ["Arrows" => PICKUP_ARROW],
1212 ],
1213 ["Armour", 0, 12,
1214 ["Helmets" => PICKUP_HELMET],
1215 ["Shields" => PICKUP_SHIELD],
1216 ["Body Armour" => PICKUP_ARMOUR],
1217 ["Boots" => PICKUP_BOOTS],
1218 ["Gloves" => PICKUP_GLOVES],
1219 ["Cloaks" => PICKUP_CLOAK],
1220 ],
1221
1222 ["Readables", 2, 0,
1223 ["Spellbooks" => PICKUP_SPELLBOOK],
1224 ["Skillscrolls" => PICKUP_SKILLSCROLL],
1225 ["Normal Books/Scrolls" => PICKUP_READABLES],
1226 ],
1227 ["Misc", 2, 5,
1228 ["Food" => PICKUP_FOOD],
1229 ["Drinks" => PICKUP_DRINK],
1230 ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1231 ["Keys" => PICKUP_KEY],
1232 ["Magical Items" => PICKUP_MAGICAL],
1233 ["Potions" => PICKUP_POTION],
1234 ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1235 ["Ignore cursed" => PICKUP_NOT_CURSED],
1236 ["Jewelery" => PICKUP_JEWELS],
1237 ["Flesh" => PICKUP_FLESH],
1238 ],
1239 ["Weight/Value ratio", 2, 17]
1240 )
1241 {
1242 my ($title, $x, $y, @bits) = @$_;
1243 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1244
1245 for (@bits) {
1246 ++$y;
1247
1248 my $mask = $_->[1];
1249 $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1250 $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1251 state => $::CFG->{pickup} & $mask,
1252 on_changed => sub {
1253 my ($box, $value) = @_;
1254
1255 if ($value) {
1256 $::CFG->{pickup} |= $mask;
1257 } else {
1258 $::CFG->{pickup} &= ~$mask;
1259 }
1260
1261 $::CONN->send_command ("pickup $::CFG->{pickup}")
1262 if defined $::CONN;
1263
1264 0
1265 });
1266
1267 ${$_->[2]} = $checkbox if $_->[2];
1268 }
1269 }
1270
1271 $table->add_at (2, 18, new CFPlus::UI::ValSlider
1272 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1273 template => ">= 99",
1274 to_value => sub { ">= " . 5 * $_[0] },
1275 on_changed => sub {
1276 my ($slider, $value) = @_;
1277
1278 $::CFG->{pickup} &= ~0xF;
1279 $::CFG->{pickup} |= int $value
1280 if $value;
1281 1;
1282 });
1283
1284 $table->add_at (3, 18, new CFPlus::UI::Button
1285 text => "set",
1286 on_activate => sub {
1287 $::CONN->send_command ("pickup $::CFG->{pickup}")
1288 if defined $::CONN;
1289 0
1290 });
1291
1292 $r
1293 }
1294
1295 my %SORT_ORDER = (
1296 type => undef,
1297 mtime => sub {
1298 my $NOW = time;
1299 sort {
1300 my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1301 my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1302
1303 ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1304 or $btime <=> $atime
1305 or $a->{type} <=> $b->{type}
1306 } @_
1307 },
1308 weight => sub { sort {
1309 $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1310 or $a->{type} <=> $b->{type}
1311 } @_ },
1312 );
1313
1314 sub inventory_widget {
1315 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1316
1317 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1318 $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1319
1320 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1321
1322 use sort 'stable';
1323
1324 $hb1->add (new CFPlus::UI::Selector
1325 value => $::CFG->{inv_sort},
1326 options => [
1327 [type => "Type/Name"],
1328 [mtime => "Recent/Normal/Locked"],
1329 [weight => "Weight/Type"],
1330 ],
1331 on_changed => sub {
1332 $::CFG->{inv_sort} = $_[1];
1333 $INV->set_sort_order ($SORT_ORDER{$_[1]});
1334 },
1335 );
1336 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1337 #TODO# update to weigh/maxweight
1338 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1339
1340 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1341 $sw1->add ($INV = new CFPlus::UI::Inventory);
1342 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1343
1344 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1345
1346 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1347
1348 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1349 $sw2->add ($INVR = new CFPlus::UI::Inventory);
1350
1351 # XXX: Call after $INVR = ... because set_opencont sets the items
1352 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1353
1354 $hb
1355 }
1356
1357 sub toggle_player_page {
1358 my ($widget) = @_;
1359
1360 if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1361 $PL_WINDOW->hide;
1362 } else {
1363 $PL_NOTEBOOK->set_current_page ($widget);
1364 $PL_WINDOW->show;
1365 }
1366 }
1367
1368 sub player_window {
1369 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1370 x => "center",
1371 y => "center",
1372 force_w => $WIDTH * 9/10,
1373 force_h => $HEIGHT * 9/10,
1374 title => "Player",
1375 name => "playerbook",
1376 has_close_button => 1
1377 ;
1378
1379 my $ntb =
1380 $PL_NOTEBOOK =
1381 new CFPlus::UI::Notebook expand => 1;
1382
1383 $ntb->add_tab (
1384 "Statistics (F2)" => $STATS_PAGE = stats_window,
1385 "Shows statistics, where all your Stats and Resistances are shown."
1386 );
1387 $ntb->add_tab (
1388 "Skills (F3)" => $SKILL_PAGE = skill_window,
1389 "Shows all your Skills."
1390 );
1391
1392 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1393 $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1394 $ntb->add_tab (
1395 "Spellbook (F4)" => $spellsw,
1396 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1397 );
1398 $ntb->add_tab (
1399 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1400 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1401 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1402 );
1403 $ntb->add_tab (Pickup => autopickup_setup,
1404 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1405
1406 $ntb->set_current_page ($INVENTORY_PAGE);
1407
1408 $plwin->add ($ntb);
1409 $plwin
1410 }
1411
1412 sub keyboard_setup {
1413 CFPlus::Macro::keyboard_setup
1414 }
1415
1416 sub help_window {
1417 my $win = new CFPlus::UI::Toplevel
1418 x => 'center',
1419 y => 'center',
1420 z => 4,
1421 name => 'doc_browser',
1422 force_w => int $WIDTH * 7/8,
1423 force_h => int $HEIGHT * 7/8,
1424 title => "Help Browser",
1425 has_close_button => 1;
1426
1427 $win->add (my $vbox = new CFPlus::UI::VBox);
1428
1429 $vbox->add (new CFPlus::UI::FancyFrame
1430 label => "Navigation",
1431 child => (my $buttons = new CFPlus::UI::HBox),
1432 );
1433 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1434 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1435
1436 my @history;
1437 my @future;
1438 my $curnode;
1439
1440 my $load_node; $load_node = sub {
1441 my ($node, $para) = @_;
1442
1443 $buttons->clear;
1444
1445 $buttons->add (new CFPlus::UI::Button
1446 text => "⇤",
1447 tooltip => "back to the starting page",
1448 on_activate => sub {
1449 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1450 unshift @future, @history;
1451 @history = ();
1452 $load_node->(@{shift @future});
1453 },
1454 );
1455
1456 if (@history) {
1457 $buttons->add (new CFPlus::UI::Button
1458 text => "⋘",
1459 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1460 on_activate => sub {
1461 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1462 $load_node->(@{pop @history});
1463 },
1464 );
1465 }
1466
1467 if (@future) {
1468 $buttons->add (new CFPlus::UI::Button
1469 text => "â‹™",
1470 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1471 on_activate => sub {
1472 push @history, [$curnode, $viewer->current_paragraph];
1473 $load_node->(@{shift @future});
1474 },
1475 );
1476 }
1477
1478 $buttons->add (new CFPlus::UI::Label text => " ");
1479
1480 my @path = CFPlus::Pod::full_path_of $node;
1481 pop @path; # drop current node
1482
1483 for my $node (@path) {
1484 $buttons->add (new CFPlus::UI::Button
1485 text => $node->{kw}[0],
1486 tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1487 on_activate => sub {
1488 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1489 $load_node->($node);
1490 },
1491 );
1492 $buttons->add (new CFPlus::UI::Label text => "/");
1493 }
1494
1495 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1496
1497 $curnode = $node;
1498
1499 $viewer->clear;
1500 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1501 $viewer->scroll_to ($para);
1502 };
1503
1504 $load_node->(CFPlus::Pod::find pod => "mainpage");
1505
1506 $CFPlus::Pod::goto_document = sub {
1507 my (@path) = @_;
1508
1509 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1510
1511 $load_node->((CFPlus::Pod::find @path)[0]);
1512 $win->show;
1513 };
1514
1515 $win
1516 }
1517
1518 sub open_string_query {
1519 my ($title, $cb, $txt, $tooltip) = @_;
1520 my $dialog = new CFPlus::UI::Toplevel
1521 x => "center",
1522 y => "center",
1523 z => 50,
1524 force_w => $WIDTH * 4/5,
1525 title => $title;
1526
1527 $dialog->add (
1528 my $e = new CFPlus::UI::Entry
1529 on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1530 on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1531 tooltip => $tooltip
1532 );
1533
1534 $e->grab_focus;
1535 $e->set_text ($txt) if $txt;
1536 $dialog->show;
1537 }
1538
1539 sub open_quit_dialog {
1540 unless ($QUIT_DIALOG) {
1541 $QUIT_DIALOG = new CFPlus::UI::Toplevel
1542 x => "center",
1543 y => "center",
1544 z => 50,
1545 title => "Really Quit?",
1546 on_key_down => sub {
1547 my ($dialog, $ev) = @_;
1548 $ev->{sym} == 27 and $dialog->hide;
1549 }
1550 ;
1551
1552 $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1553
1554 $vb->add (new CFPlus::UI::Label
1555 text => "You should find a savebed and apply it first!",
1556 max_w => $WIDTH * 0.25,
1557 ellipsize => 0,
1558 );
1559 $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1560 $hb->add (new CFPlus::UI::Button
1561 text => "Ok",
1562 expand => 1,
1563 on_activate => sub { $QUIT_DIALOG->hide; 0 },
1564 );
1565 $hb->add (new CFPlus::UI::Button
1566 text => "Quit anyway",
1567 expand => 1,
1568 on_activate => sub { exit },
1569 );
1570 }
1571
1572 $QUIT_DIALOG->show;
1573 $QUIT_DIALOG->grab_focus;
1574 }
1575
1576 sub show_tip_of_the_day {
1577 # find all tips
1578 my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1579
1580 CFPlus::DB::get state => "tip_of_the_day", sub {
1581 my ($todindex) = @_;
1582 $todindex = 0 if $todindex >= @tod;
1583 CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
1584
1585 # create dialog
1586 my $dialog;
1587
1588 my $close = sub {
1589 $dialog->destroy;
1590 };
1591
1592 $dialog = new CFPlus::UI::Toplevel
1593 x => "center",
1594 y => "center",
1595 z => 3,
1596 name => 'tip_of_the_day',
1597 force_w => int $WIDTH * 4/9,
1598 force_h => int $WIDTH * 2/9,
1599 title => "Tip of the day #" . (1 + $todindex),
1600 child => my $vbox = new CFPlus::UI::VBox,
1601 has_close_button => 1,
1602 on_delete => $close,
1603 ;
1604
1605 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1606 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1607 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1608
1609 $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
1610
1611 $table->add_at (0, 0, new CFPlus::UI::Button
1612 text => "Close",
1613 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>.",
1614 on_activate => $close,
1615 );
1616
1617 $table->add_at (2, 0, new CFPlus::UI::Button
1618 text => "Next",
1619 tooltip => "Show the next <b>Tip of the day</b>.",
1620 on_activate => sub {
1621 $close->();
1622 &show_tip_of_the_day;
1623 },
1624 );
1625
1626 $dialog->show;
1627 };
1628 }
1629
1630 sub sdl_init {
1631 CFPlus::SDL_Init
1632 and die "SDL::Init failed!\n";
1633 }
1634
1635 sub video_init {
1636 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1637
1638 my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1639
1640 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1641 $FULLSCREEN = $CFG->{fullscreen};
1642 $FAST = $CFG->{fast};
1643
1644 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
1645 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1646
1647 $SDL_ACTIVE = 1;
1648 $LAST_REFRESH = time - 0.01;
1649
1650 CFPlus::OpenGL::init;
1651
1652 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1653
1654 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1655
1656 #############################################################################
1657
1658 if ($DEBUG_STATUS) {
1659 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1660 } else {
1661 # create the widgets
1662
1663 $DEBUG_STATUS = new CFPlus::UI::Label
1664 padding => 0,
1665 z => 100,
1666 force_x => "max",
1667 force_y => 0;
1668 $DEBUG_STATUS->show;
1669
1670 $STATUSBOX = new CFPlus::UI::Statusbox;
1671 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1672
1673 (new CFPlus::UI::Frame
1674 bg => [0, 0, 0, 0.4],
1675 force_x => 0,
1676 force_y => "max",
1677 child => $STATUSBOX,
1678 )->show;
1679
1680 CFPlus::UI::Toplevel->new (
1681 title => "Map",
1682 name => "mapmap",
1683 x => 0,
1684 y => $FONTSIZE + 8,
1685 border_bg => [1, 1, 1, 192/255],
1686 bg => [1, 1, 1, 0],
1687 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1688 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1689 ),
1690 )->show;
1691
1692 $MAPWIDGET = new CFPlus::MapWidget;
1693 $MAPWIDGET->connect (activate_console => sub {
1694 my ($mapwidget, $preset) = @_;
1695
1696 $MESSAGE_WINDOW->activate_console ($preset)
1697 if $MESSAGE_WINDOW;
1698 });
1699 $MAPWIDGET->show;
1700 $MAPWIDGET->grab_focus;
1701
1702 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1703 title => "Setup",
1704 name => "setup_dialog",
1705 x => 'center',
1706 y => 'center',
1707 z => 2,
1708 force_w => $::WIDTH * 0.6,
1709 force_h => $::HEIGHT * 0.6,
1710 has_close_button => 1,
1711 ;
1712
1713 $METASERVER = metaserver_dialog;
1714 $MESSAGE_WINDOW = new CFPlus::UI::MessageWindow;
1715
1716 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1717 filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1718
1719 $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
1720 "Configure the server to play on, your username, password and other server-related options.");
1721 $SETUP_NOTEBOOK->add_tab (Client => client_setup,
1722 "Configure various client-specific settings.");
1723 $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
1724 "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1725 $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
1726 "Configure the use of audio, sound effects and background music.");
1727 $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1728 "Lets you define, edit and delete key bindings."
1729 . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1730 . "with nothing set and the recording started. After doing the actions you "
1731 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1732 . "After pressing the combo the binding will be saved automatically and the "
1733 . "binding editor closes");
1734 $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
1735 "Some debuggin' options. Do not ask.");
1736
1737 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1738
1739 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1740 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1741
1742 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
1743 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1744
1745 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
1746
1747 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1748 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1749
1750 $BUTTONBAR->add (new CFPlus::UI::Button
1751 text => "Save Config",
1752 tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1753 on_activate => sub {
1754 $::CFG->{layout} = CFPlus::UI::get_layout;
1755 CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1756 status "Configuration Saved";
1757 0
1758 },
1759 );
1760
1761 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1762 tooltip => "View Documentation");
1763
1764
1765 $BUTTONBAR->add (new CFPlus::UI::Button
1766 text => "Quit",
1767 tooltip => "Terminates the program",
1768 on_activate => sub {
1769 if ($CONN) {
1770 open_quit_dialog;
1771 } else {
1772 exit;
1773 }
1774 0
1775 },
1776 );
1777
1778 $BUTTONBAR->show;
1779 $SETUP_DIALOG->show;
1780 }
1781
1782 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1783 }
1784
1785 sub video_shutdown {
1786 CFPlus::OpenGL::shutdown;
1787
1788 undef $SDL_ACTIVE;
1789 }
1790
1791 sub audio_channel_finished {
1792 my ($channel) = @_;
1793
1794 # warn "channel $channel finished\n";#d#
1795 }
1796
1797 our %AUDIO_PLAY;
1798 our %AUDIO_CHUNK;
1799
1800 sub audio_sound_push($) {
1801 my ($face) = @_;
1802
1803 if (my $chunk = $AUDIO_CHUNK{$face}) {
1804 for (grep $_->[0] >= Event::time, @{(delete $AUDIO_PLAY{$face}) || []}) {
1805 my (undef, $dx, $dy, $vol) = @$_;
1806
1807 my $channel = $chunk->play
1808 or return;
1809
1810 $channel->volume (128 + $vol);
1811 $dx = $dx / 10 * 255;
1812 $channel->set_panning ((min 255, 255 - $dx), (min 255, 255 + $dx));
1813 # my $angle = $dx ? : $dx < 0 ?
1814 # my $distance = -$vol;
1815 # $channel->set_position ($angle, $distance);
1816 }
1817 } else {
1818 # sound_meta not set means data is in flight either way
1819 my $meta = $CONN->{sound_meta}{$face}
1820 or return;
1821
1822 # fetch from database
1823 CFPlus::DB::get res_data => $meta->{name}, sub {
1824 my $vol = $meta->{meta}{volume} || 100;
1825 my $rwops = new CFPlus::RW $_[0];
1826 my $chunk = new CFPlus::MixChunk $rwops;
1827 $chunk->volume ($vol * 128 / 100);
1828 $AUDIO_CHUNK{$face} = $chunk;
1829
1830 audio_sound_push ($face);
1831 };
1832 }
1833 }
1834
1835 sub audio_sound_play {
1836 my ($face, $dx, $dy, $vol) = @_;
1837
1838 $SDL_MIXER
1839 or return;
1840
1841 my $queue = $AUDIO_PLAY{$face} ||= [];
1842 push @$queue, [Event::time + 0.2, $dx, $dy, $vol]; # delay sound by max. 0.2s
1843 audio_sound_push $face
1844 unless @$queue > 1;
1845 }
1846
1847 sub audio_music_set {
1848 my ($songs) = @_;
1849
1850 my @want =
1851 grep $_,
1852 map $CONN->{music_meta}{$_},
1853 @$songs;
1854
1855 if (@want) {
1856 @MUSIC_WANT = @want;
1857 &audio_music_changed ();
1858 }
1859 }
1860
1861 sub audio_music_start {
1862 my $meta = $MUSIC_PLAYING_META;
1863
1864 CFPlus::DB::get res_data => $meta->{name}, sub {
1865 return unless $SDL_MIXER;
1866
1867 # music might have changed...
1868 $meta eq $MUSIC_PLAYING_META
1869 or return &audio_music_start ();
1870
1871 $MUSIC_PLAYING_DATA = \$_[0];
1872
1873 my $rwops = $meta->{path}
1874 ? new_from_file CFPlus::RW $meta->{path}
1875 : new CFPlus::RW $$MUSIC_PLAYING_DATA;
1876
1877 $MUSIC_PLAYER = new CFPlus::MixMusic $rwops
1878 or ((warn CFPlus::Mix_GetError), return); # pretty fatal error
1879
1880 my $NOW = time;
1881
1882 if ($MUSIC_PLAYING_META->{stop_time} > $NOW - $MUSIC_RESUME) {
1883 my $pos = $MUSIC_PLAYING_META->{stop_pos};
1884 $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
1885 $MUSIC_START = time - $pos;
1886 } else {
1887 $MUSIC_PLAYER->play (0);
1888 $MUSIC_START = time;
1889 }
1890
1891 delete $MUSIC_PLAYING_META->{stop_time};
1892 delete $MUSIC_PLAYING_META->{stop_pos};
1893 }
1894 }
1895
1896 sub audio_music_changed {
1897 return unless $CFG->{bgm_enable};
1898 return unless $SDL_MIXER;
1899
1900 # default MUSIC_WANT == MUSIC_DEFAULT
1901 @MUSIC_WANT = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_WANT;
1902
1903 # if the currently playing song is acceptable, let it continue
1904 return if $MUSIC_PLAYING_META
1905 && grep $MUSIC_PLAYING_META == $_, @MUSIC_WANT;
1906
1907 my $NOW = time;
1908
1909 if ($MUSIC_PLAYING_META) {
1910 $MUSIC_PLAYING_META->{stop_time} = $NOW;
1911 $MUSIC_PLAYING_META->{stop_pos} = $NOW - $MUSIC_START;
1912 CFPlus::MixMusic::fade_out 1000;
1913 } else {
1914 # sort by stop time, oldest first
1915 @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
1916
1917 # if the most recently-played piece played very recently,
1918 # resume it, else choose the oldest piece for rotation.
1919 $MUSIC_PLAYING_META =
1920 $MUSIC_WANT[-1]{stop_time} > $NOW - $MUSIC_RESUME
1921 ? $MUSIC_WANT[-1]
1922 : $MUSIC_WANT[0];
1923
1924 audio_music_start;
1925 }
1926 }
1927
1928 sub audio_music_finished {
1929 undef $MUSIC_PLAYER;
1930 undef $MUSIC_PLAYING_META;
1931 undef $MUSIC_PLAYING_DATA;
1932
1933 audio_music_changed;
1934 }
1935
1936 sub audio_init {
1937 if ($CFG->{audio_enable}) {
1938 $ENV{MIX_EFFECTSMAXSPEED} = 1;
1939 $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1940
1941 unless ($SDL_MIXER) {
1942 status "Unable to open sound device: there will be no sound";
1943 return;
1944 }
1945
1946 CFPlus::Mix_AllocateChannels 16;
1947 CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1948
1949 audio_music_finished;
1950 } else {
1951 undef $SDL_MIXER;
1952 }
1953 }
1954
1955 sub audio_shutdown {
1956 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1957 undef $SDL_MIXER;
1958 @SOUNDS = ();
1959 %AUDIO_CHUNKS = ();
1960 }
1961
1962 my %animate_object;
1963 my $animate_timer;
1964
1965 my $fps = 9;
1966
1967 my %demo;#d#
1968
1969 sub force_refresh {
1970 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1971 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1972
1973 $CFPlus::UI::ROOT->draw;
1974
1975 $WANT_REFRESH = 0;
1976 $CAN_REFRESH = 0;
1977 $LAST_REFRESH = $NOW;
1978
1979 CFPlus::SDL_GL_SwapBuffers;
1980 }
1981
1982 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1983 $NOW = time;
1984
1985 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1986 for CFPlus::poll_events;
1987
1988 if (%animate_object) {
1989 $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1990 ++$WANT_REFRESH;
1991 }
1992
1993 if ($WANT_REFRESH) {
1994 force_refresh;
1995 } else {
1996 $CAN_REFRESH = 1;
1997 }
1998 });
1999
2000 sub animation_start {
2001 my ($widget) = @_;
2002 $animate_object{$widget} = $widget;
2003 }
2004
2005 sub animation_stop {
2006 my ($widget) = @_;
2007 delete $animate_object{$widget};
2008 }
2009
2010 %SDL_CB = (
2011 CFPlus::SDL_QUIT => sub {
2012 exit;
2013 },
2014 CFPlus::SDL_VIDEORESIZE => sub {
2015 },
2016 CFPlus::SDL_VIDEOEXPOSE => sub {
2017 CFPlus::UI::full_refresh;
2018 },
2019 CFPlus::SDL_ACTIVEEVENT => sub {
2020 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2021 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2022 # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2023 # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2024 # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2025 # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2026 },
2027 CFPlus::SDL_KEYDOWN => sub {
2028 if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2029 # alt-enter
2030 $FULLSCREEN_ENABLE->toggle;
2031 video_shutdown;
2032 video_init;
2033 } else {
2034 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2035 }
2036 },
2037 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2038 CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2039 CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2040 CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2041 CFPlus::SDL_USEREVENT => sub {
2042 if ($_[0]{code} == 1) {
2043 audio_channel_finished $_[0]{data1};
2044 } elsif ($_[0]{code} == 0) {
2045 audio_music_finished;
2046 }
2047 },
2048 );
2049
2050 #############################################################################
2051
2052 $SIG{INT} = $SIG{TERM} = sub { exit };
2053
2054 {
2055 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2056 CFPlus::DB::Server::run;
2057
2058 CFPlus::UI::set_layout ($::CFG->{layout});
2059
2060 my %DEF_CFG = (
2061 sdl_mode => 0,
2062 width => 640,
2063 height => 480,
2064 fullscreen => 0,
2065 fast => 0,
2066 map_scale => 1,
2067 fow_enable => 1,
2068 fow_intensity => 0,
2069 map_smoothing => 1,
2070 gui_fontsize => 1,
2071 log_fontsize => 0.7,
2072 gauge_fontsize => 1,
2073 gauge_size => 0.35,
2074 stat_fontsize => 0.7,
2075 mapsize => 100,
2076 audio_enable => 1,
2077 bgm_enable => 1,
2078 bgm_volume => 0.25,
2079 output_sync => 1,
2080 output_count => 1,
2081 output_rate => "",
2082 pickup => 0,
2083 inv_sort => "mtime",
2084 default => "profile", # default profile
2085 show_tips => 1,
2086 logview_max_par => 1000,
2087 );
2088
2089 while (my ($k, $v) = each %DEF_CFG) {
2090 $CFG->{$k} = $v unless exists $CFG->{$k};
2091 }
2092
2093 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2094 $PROFILE = $CFG->{profile}{default};
2095
2096 # convert old bindings (only default profile matters)
2097 if (my $bindings = delete $PROFILE->{bindings}) {
2098 while (my ($mod, $syms) = each %$bindings) {
2099 while (my ($sym, $cmds) = each %$syms) {
2100 push @{ $PROFILE->{macro} }, {
2101 accelkey => [$mod*1, $sym*1],
2102 action => $cmds,
2103 };
2104 }
2105 }
2106 }
2107
2108 sdl_init;
2109
2110 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2111 @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2112 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2113
2114 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2115
2116 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2117
2118 {
2119 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2120 DejaVuSans.ttf
2121 DejaVuSansMono.ttf
2122 DejaVuSans-Bold.ttf
2123 DejaVuSansMono-Bold.ttf
2124 DejaVuSans-Oblique.ttf
2125 DejaVuSansMono-Oblique.ttf
2126 DejaVuSans-BoldOblique.ttf
2127 DejaVuSansMono-BoldOblique.ttf
2128 );
2129
2130 CFPlus::add_font $_ for @fonts;
2131
2132 CFPlus::pango_init;
2133
2134 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2135 $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2136
2137 $FONT_PROP->make_default;
2138 }
2139
2140 # compare mono (ft) vs. rgba (cairo)
2141 # ft - 1.8s, cairo 3s, even in alpha-only mode
2142 # for my $rgba (0..1) {
2143 # my $t1 = Time::HiRes::time;
2144 # for (1..1000) {
2145 # my $layout = CFPlus::Layout->new ($rgba);
2146 # $layout->set_text ("hallo" x 100);
2147 # $layout->render;
2148 # }
2149 # my $t2 = Time::HiRes::time;
2150 # warn $t2-$t1;
2151 # }
2152
2153 $startup_done->();
2154
2155 video_init;
2156 audio_init;
2157 }
2158
2159 show_tip_of_the_day if $CFG->{show_tips};
2160
2161 Event::loop;
2162 #CFPlus::SDL_Quit;
2163 #CFPlus::_exit 0;
2164
2165 END {
2166 CFPlus::SDL_Quit;
2167 CFPlus::DB::Server::stop;
2168 }
2169
2170 =head1 NAME
2171
2172 cfplus - A Crossfire+ and Crossfire game client
2173
2174 =head1 SYNOPSIS
2175
2176 Just run it - no commandline arguments are supported.
2177
2178 =head1 USAGE
2179
2180 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2181 fullscreen and interactively.
2182
2183 =head1 DEBUGGING
2184
2185
2186 CFPLUS_DEBUG - environment variable
2187
2188 1 draw borders around widgets
2189 2 add low-level widget info to tooltips
2190 4 show fps
2191 8 suppress tooltips
2192
2193 =head1 AUTHOR
2194
2195 Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2196
2197
2198