ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.64
Committed: Wed Jun 7 06:28:30 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.63: +40 -33 lines
Log Message:
add license, fix pod formatting

File Contents

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