ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.119
Committed: Fri Sep 29 00:56:06 2006 UTC (17 years, 8 months ago) by root
Branch: MAIN
Changes since 1.118: +127 -69 lines
Log Message:
* implemented tip of the day window
* created some tips of the day, please add more
* rationalised errro report (and put client into debug mode :)
* add some random keyboard documentation

File Contents

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