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