ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.118
Committed: Tue Aug 22 06:37:30 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_52, rel-0_5, rel-0_51
Changes since 1.117: +3 -1 lines
Log Message:
*** empty log message ***

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