ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.66
Committed: Thu Jun 8 19:43:26 2006 UTC (17 years, 11 months ago) by elmex
Branch: MAIN
Changes since 1.65: +20 -0 lines
Log Message:
added weight/value slider to pickup

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 root 1.65 sub debug_setup {
631     my $table = new CFClient::UI::Table;
632    
633     $table->add (0, 0, new CFClient::UI::Label text => "Widget Borders");
634     $table->add (1, 0, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1 });
635     $table->add (0, 1, new CFClient::UI::Label text => "Tooltip Widget Info");
636     $table->add (1, 1, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2 });
637     $table->add (0, 2, new CFClient::UI::Label text => "Show FPS");
638     $table->add (1, 2, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4 });
639     $table->add (0, 3, new CFClient::UI::Label text => "Suppress Tooltips");
640     $table->add (1, 3, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8 });
641    
642     my @default_smooth = (0.05, 0.13, 0.05, 0.13, 0.30, 0.13, 0.05, 0.13, 0.05);
643    
644     for my $x (0..2) {
645     for my $y (0 .. 2) {
646     $table->add ($x + 3, $y,
647     new CFClient::UI::Entry
648     text => $default_smooth[$x * 3 + $y],
649     on_changed => sub { $MAP->{smooth_matrix}[$x * 3 + $y] = $_[1] if $MAP; 0 },
650     );
651     }
652     }
653    
654    
655     $table
656     }
657 elmex 1.24
658 root 1.60 sub stats_window {
659 elmex 1.19 my $tgw = new CFClient::UI::FancyFrame
660 root 1.30 y => $HEIGHT * (2/8),
661     x => "max",
662 elmex 1.19 title => "Stats",
663 root 1.30 name => "stats_window";
664 root 1.1
665     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
666     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
667     can_hover => 1, can_events => 1,
668     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
669     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
670     can_hover => 1, can_events => 1,
671     tooltip => "The map you are currently on (if supported by the server).");
672    
673 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
674     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
675     can_hover => 1, can_events => 1,
676 root 1.15 tooltip => "The weight of the player including all inventory items.");
677 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
678     can_hover => 1, can_events => 1,
679 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
680 elmex 1.5
681    
682 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
683     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
684    
685     my $color2 = [1, 1, 0];
686    
687     for (
688     [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"],
689     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
690     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
691     [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"],
692     [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"],
693     [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"],
694     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
695    
696     [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."],
697     [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."],
698     [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."],
699     [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."],
700     [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."],
701     [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."],
702     ) {
703     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
704    
705     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
706     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
707     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
708     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
709     }
710    
711     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
712    
713     my $row = 0;
714     my $col = 0;
715    
716     my %resist_names = (
717     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.)",
718     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.)",
719     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
720     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
721     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
722     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
723     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
724     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
725     pois => "<b>Poison</b> (resistance to getting poisoned)",
726     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
727     deat => "<b>Death</b> (resistance against death spells)",
728     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
729     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
730     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)",
731     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
732     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
733     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
734     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
735     );
736     for (qw/slow holyw conf fire depl magic
737     drain acid pois para deat phys
738     blind fear tund elec cold ghit/)
739     {
740     $tbl2->add ($col, $row,
741     $STATWIDS->{"res_$_"} =
742     new CFClient::UI::Label
743     font => $FONT_FIXED,
744     template => "-100%",
745     align => +1,
746     valign => 0,
747     can_events => 1,
748     can_hover => 1,
749     tooltip => $resist_names{$_},
750     );
751     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
752     font => $FONT_FIXED,
753     can_hover => 1,
754     can_events => 1,
755     image => "ui/resist/resist_$_.png",
756     tooltip => $resist_names{$_},
757     );
758    
759     $row++;
760     if ($row % 6 == 0) {
761     $col += 2;
762     $row = 0;
763     }
764     }
765    
766     &set_stats_window_fontsize;
767     update_stats_window ({});
768    
769     $tgw
770     }
771    
772 root 1.48 sub formsep($) {
773     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
774 root 1.1 }
775    
776     sub update_stats_window {
777     my ($stats) = @_;
778    
779 root 1.12 # I love text protocols...
780    
781     my $hp = $stats->{+CS_STAT_HP} * 1;
782     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
783     my $sp = $stats->{+CS_STAT_SP} * 1;
784     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
785     my $fo = $stats->{+CS_STAT_FOOD} * 1;
786 root 1.1 my $fo_m = 999;
787 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
788     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
789 root 1.1
790     $GAUGES->{hp} ->set_value ($hp, $hp_m);
791     $GAUGES->{mana} ->set_value ($sp, $sp_m);
792     $GAUGES->{food} ->set_value ($fo, $fo_m);
793     $GAUGES->{grace} ->set_value ($gr, $gr_m);
794 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
795     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
796     my $rng = $stats->{+CS_STAT_RANGE};
797 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
798     $GAUGES->{range} ->set_text ("Rng: " . $rng);
799 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
800 root 1.1 $title =~ s/^Player: //;
801     $STATWIDS->{title} ->set_text ("Title: " . $title);
802    
803 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
804     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
805     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
806     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
807     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
808     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
809     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
810     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
811     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
812     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
813     $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_ARMOUR});
814     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
815     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
816 root 1.1
817 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
818 elmex 1.5
819 root 1.12 # TODO: replace by CS_STAT_RES_xxx constants
820 root 1.1 my %tbl = (
821     phys => 100,
822     magic => 101,
823     fire => 102,
824     elec => 103,
825     cold => 104,
826     conf => 105,
827     acid => 106,
828     drain => 107,
829     ghit => 108,
830     pois => 109,
831     slow => 110,
832     para => 111,
833     tund => 112,
834     fear => 113,
835     depl => 113,
836     deat => 115,
837     holyw => 116,
838 root 1.12 blind => 117,
839 root 1.1 );
840    
841 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
842     for keys %tbl;
843 root 1.1 }
844    
845     my $METASERVER_ATIME;
846    
847     sub update_metaserver {
848     return if $METASERVER_ATIME > time;
849     $METASERVER_ATIME = time + 60;
850    
851     my $table = $METASERVER->{table};
852     $table->clear;
853     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
854    
855     my $buf;
856    
857     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
858    
859     unless ($fh) {
860     $label->set_text ("unable to contact metaserver: $!");
861     return;
862     }
863    
864     Event->io (fd => $fh, poll => 'r', cb => sub {
865     my $res = sysread $fh, $buf, 8192, length $buf;
866    
867     if (!defined $res) {
868     $_[0]->w->cancel;
869     $label->set_text ("error while retrieving server list: $!");
870     } elsif ($res == 0) {
871     $_[0]->w->cancel;
872     status "server list retrieved";
873    
874     utf8::decode $buf if utf8::valid $buf;
875    
876     $table->clear;
877    
878 root 1.62 my @tip = (
879     "The current number of users logged in on the server.",
880     "The hostname of the server.",
881     "The time this server has been running without being restarted.",
882     "The server software version - a '+' indicates a Crossfire+ server.",
883     "Short information about this server provided by its admins.",
884     );
885     my @col = qw(#Users Host Uptime Version Description);
886     $table->add ($_, 0, new CFClient::UI::Label
887     can_hover => 1, can_events => 1,
888     align => 0, fg => [1, 1, 0],
889     text => $col[$_], tooltip => $tip[$_])
890     for 0 .. $#col;
891 root 1.1
892     my @align = qw(1 0 1 1 -1);
893    
894     my $y = 0;
895     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
896     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
897    
898     for ($desc) {
899     s/<br>/\n/gi;
900     s/<li>/\n· /gi;
901     s/<.*?>//sgi;
902     s/&/&amp;/g;
903     s/</&lt;/g;
904     s/>/&gt;/g;
905     }
906    
907     $uptime = sprintf "%dd %02d:%02d:%02d",
908     (int $m->[8] / 86400),
909     (int $m->[8] / 3600) % 24,
910     (int $m->[8] / 60) % 60,
911     $m->[8] % 60;
912    
913     $m = [$users, $host, $uptime, $version, $desc];
914    
915     $y++;
916    
917 root 1.62 $table->add (scalar @$m, $y, new CFClient::UI::VBox children => [
918     (new CFClient::UI::Button
919     text => "Use",
920     tooltip => "Put this server into the <b>Host:Port</b> field",
921     on_activate => sub {
922     $HOST_ENTRY->set_text ($CFG->{host} = $host);
923     $METASERVER->hide;
924     },
925     ),
926 root 1.1 (new CFClient::UI::Empty expand => 1),
927     ]);
928    
929 root 1.62 $table->add ($_, $y, new CFClient::UI::Label
930     ellipsise => 0,
931     align => $align[$_],
932     text => $m->[$_],
933     tooltip => $tip[$_],
934     can_hover => 1,
935     can_events => 1,
936     fontsize => 0.8)
937 root 1.1 for 0 .. $#$m;
938     }
939     }
940     });
941     }
942    
943 root 1.40 sub metaserver_dialog {
944     my $dialog = new CFClient::UI::FancyFrame
945 root 1.62 title => "Server List",
946     name => 'metaserver_dialog',
947     x => 'center',
948     y => 'center',
949     z => 3,
950     force_h => $::HEIGHT * 0.4,
951     child => (my $vbox = new CFClient::UI::VBox),
952 root 1.40 on_visibility_change => sub {
953     update_metaserver if $_[1];
954     },
955     ;
956    
957 root 1.62 $dialog->{table} = new CFClient::UI::Table;
958    
959     $vbox->add (new CFClient::UI::ScrolledWindow expand => 1, child => $dialog->{table});
960 root 1.40
961     $dialog
962     }
963    
964 root 1.1 sub server_setup {
965 root 1.49 my $vbox = new CFClient::UI::VBox;
966 elmex 1.19
967 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
968     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
969    
970     {
971     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
972    
973     $vbox->add (
974 root 1.40 $HOST_ENTRY = new CFClient::UI::Entry
975 root 1.1 expand => 1,
976     text => $CFG->{host},
977     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
978 root 1.18 on_changed => sub {
979 root 1.1 my ($self, $value) = @_;
980     $CFG->{host} = $value;
981     }
982     );
983    
984     $METASERVER = metaserver_dialog;
985    
986 root 1.40 $vbox->add (new CFClient::UI::Button
987     expand => 1,
988     text => "Server List",
989     other => $METASERVER,
990 root 1.1 tooltip => "Show a list of available crossfire servers",
991 root 1.40 on_activate => sub { $METASERVER->toggle_visibility },
992 root 1.62 on_visibility_change => sub { $METASERVER->hide unless $_[1] },
993 root 1.1 );
994     }
995    
996     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
997     $table->add (1, 4, new CFClient::UI::Entry
998     text => $CFG->{user},
999     tooltip => "The name of your character on the server",
1000 root 1.18 on_changed => sub {
1001 root 1.1 my ($self, $value) = @_;
1002     $CFG->{user} = $value;
1003     }
1004     );
1005    
1006     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
1007     $table->add (1, 5, new CFClient::UI::Entry
1008     text => $CFG->{password},
1009     hidden => 1,
1010     tooltip => "The password for your character",
1011 root 1.18 on_changed => sub {
1012 root 1.1 my ($self, $value) = @_;
1013     $CFG->{password} = $value;
1014     }
1015     );
1016    
1017     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
1018     $table->add (1, 7, new CFClient::UI::Slider
1019 root 1.30 force_w => 100,
1020 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1021     tooltip => "This is the size of the portion of the map update the server sends you. "
1022     . "If you set this to a high value you will be able to see further, "
1023     . "but you also increase bandwidth requirements and latency. "
1024     . "This option is only used once at log-in.",
1025 root 1.18 on_changed => sub {
1026 root 1.1 my ($self, $value) = @_;
1027    
1028     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
1029     },
1030     );
1031    
1032     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1033     $table->add (1, 8, new CFClient::UI::CheckBox
1034     state => $CFG->{face_prefetch},
1035     tooltip => "<b>Background Image Prefetch</b>\n\n"
1036     . "If enabled, the client automatically pre-fetches images from the server. "
1037     . "This might increase or create lag, but increases the chances "
1038     . "of faces being ready for display when you encounter them. "
1039     . "It also uses up server bandwidth on every connect, "
1040     . "so only set it if you really need to prefetch images. "
1041     . "This option can be set and unset any time.",
1042 root 1.18 on_changed => sub { $CFG->{face_prefetch} = $_[1] },
1043 root 1.1 );
1044    
1045     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
1046     $table->add (1, 9, new CFClient::UI::Entry
1047     text => $CFG->{output_count},
1048     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1049 root 1.18 on_changed => sub { $CFG->{output_count} = $_[1] },
1050 root 1.1 );
1051    
1052     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
1053     $table->add (1, 10, new CFClient::UI::Entry
1054     text => $CFG->{output_sync},
1055     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1056 root 1.18 on_changed => sub { $CFG->{output_sync} = $_[1] },
1057 root 1.1 );
1058    
1059     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
1060     expand => 1,
1061     align => 0,
1062     text => "Login",
1063 root 1.18 on_activate => sub {
1064 root 1.1 $CONN ? stop_game
1065     : start_game;
1066     },
1067     );
1068    
1069 root 1.49 $table->add (0, 12, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
1070     $table->add (1, 12, my $saycmd = new CFClient::UI::Entry
1071     text => $CFG->{say_command},
1072     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. "
1073     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
1074     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
1075     on_changed => sub {
1076     my ($self, $value) = @_;
1077     $CFG->{say_command} = $value;
1078     }
1079     );
1080    
1081     $vbox
1082 root 1.1 }
1083    
1084     sub message_window {
1085     my $window = new CFClient::UI::FancyFrame
1086 elmex 1.16 name => "message_window",
1087 root 1.1 title => "Messages",
1088     border_bg => [1, 1, 1, 1],
1089     bg => [0, 0, 0, 0.75],
1090 root 1.30 x => "max",
1091     y => 0,
1092 root 1.60 force_w => $::WIDTH * 0.4,
1093     force_h => $::HEIGHT * 0.5,
1094 root 1.1 child => (my $vbox = new CFClient::UI::VBox);
1095    
1096     $vbox->add ($LOGVIEW);
1097    
1098     $vbox->add (my $input = new CFClient::UI::Entry
1099     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
1100     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
1101     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
1102     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
1103 root 1.18 on_focus_in => sub {
1104 root 1.1 my ($input, $prev_focus) = @_;
1105    
1106     delete $input->{refocus_map};
1107    
1108     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
1109     $input->{refocus_map} = 1;
1110     }
1111     delete $input->{auto_activated};
1112     },
1113 root 1.18 on_activate => sub {
1114 root 1.1 my ($input, $text) = @_;
1115     $input->set_text ('');
1116    
1117 elmex 1.46 if ($text =~ /^\/(.*)/) {
1118 root 1.1 $::CONN->user_send ($1);
1119     } else {
1120     my $say_cmd = $::CFG->{say_command} || 'say';
1121     $::CONN->user_send ("$say_cmd $text");
1122     }
1123     if ($input->{refocus_map}) {
1124     delete $input->{refocus_map};
1125     $MAPWIDGET->focus_in
1126     }
1127     },
1128 root 1.18 on_escape => sub {
1129 root 1.1 $MAPWIDGET->focus_in
1130     },
1131     );
1132    
1133     $CONSOLE = {
1134     window => $window,
1135 root 1.30 input => $input,
1136 root 1.1 };
1137    
1138     $window
1139     }
1140    
1141     sub open_quit_dialog {
1142     unless ($QUIT_DIALOG) {
1143 root 1.30 $QUIT_DIALOG = new CFClient::UI::FancyFrame
1144     x => "center",
1145     y => "center",
1146 root 1.55 z => 50,
1147 root 1.30 title => "Really Quit?",
1148     ;
1149 root 1.1
1150     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
1151    
1152     $vb->add (new CFClient::UI::Label
1153     text => "You should find a savebed and apply it first!",
1154     max_w => $WIDTH * 0.25,
1155     ellipsize => 0,
1156     );
1157     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
1158     $hb->add (new CFClient::UI::Button
1159     text => "Ok",
1160     expand => 1,
1161 root 1.18 on_activate => sub { $QUIT_DIALOG->hide },
1162 root 1.1 );
1163     $hb->add (new CFClient::UI::Button
1164     text => "Quit anyway",
1165     expand => 1,
1166 root 1.18 on_activate => sub { exit },
1167 root 1.1 );
1168 root 1.21 }
1169 root 1.1
1170 root 1.21 $QUIT_DIALOG->show;
1171 root 1.1 }
1172    
1173 root 1.49 sub autopickup_setup {
1174 root 1.51 my $table = new CFClient::UI::Table;
1175 elmex 1.44
1176 elmex 1.43 for (
1177 root 1.51 ["General", 0, 0,
1178 root 1.58 ["Enable autopickup" => PICKUP_NEWMODE],
1179     ["Inhibit autopickup" => PICKUP_INHIBIT],
1180     ["Stop before pickup" => PICKUP_STOP],
1181     ["Debug autopickup" => PICKUP_DEBUG],
1182 root 1.51 ],
1183     ["Weapons", 0, 6,
1184 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1185     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1186     ["Bows" => PICKUP_BOW],
1187     ["Arrows" => PICKUP_ARROW],
1188 root 1.51 ],
1189     ["Armour", 0, 12,
1190 root 1.58 ["Helmets" => PICKUP_HELMET],
1191     ["Shields" => PICKUP_SHIELD],
1192     ["Body Armour" => PICKUP_ARMOUR],
1193     ["Boots" => PICKUP_BOOTS],
1194     ["Gloves" => PICKUP_GLOVES],
1195     ["Cloaks" => PICKUP_CLOAK],
1196 root 1.51 ],
1197    
1198     ["Readables", 2, 2,
1199 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1200     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1201     ["Normal Books/Scrolls" => PICKUP_READABLES],
1202 root 1.51 ],
1203     ["Misc", 2, 7,
1204 root 1.58 ["Food" => PICKUP_FOOD],
1205     ["Drinks" => PICKUP_DRINK],
1206     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1207     ["Keys" => PICKUP_KEY],
1208     ["Magical Items" => PICKUP_MAGICAL],
1209     ["Potions" => PICKUP_POTION],
1210     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1211     ["Ignore cursed" => PICKUP_NOT_CURSED],
1212     ["Jewelery" => PICKUP_JEWELS],
1213 root 1.51 ],
1214 elmex 1.66 ["Weight/Value ratio", 2, 17]
1215 elmex 1.43 )
1216     {
1217 root 1.51 my ($title, $x, $y, @bits) = @$_;
1218     $table->add ($x, $y, new CFClient::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1219    
1220     for (@bits) {
1221     ++$y;
1222    
1223 elmex 1.43 my $mask = $_->[1];
1224 root 1.51 $table->add ($x , $y, new CFClient::UI::Label text => $_->[0], align => 1, expand => 1);
1225     $table->add ($x+1, $y, new CFClient::UI::CheckBox
1226 elmex 1.43 state => $CFG->{pickup} & $mask,
1227     on_changed => sub {
1228     my ($box, $value) = @_;
1229 root 1.63
1230 elmex 1.43 if ($value) {
1231 elmex 1.45 $::CFG->{pickup} |= $mask;
1232 elmex 1.43 } else {
1233 root 1.63 $::CFG->{pickup} &= ~$mask;
1234 elmex 1.43 }
1235 root 1.63
1236     $::CONN->send_command ("pickup $::CFG->{pickup}")
1237 elmex 1.45 if defined $::CONN;
1238 elmex 1.43 });
1239     }
1240     }
1241    
1242 elmex 1.66 $table->add (2, 18, new CFClient::UI::ValSlider
1243     range => [0, 0, 16, 1, 1],
1244     to_value => sub { ">= " . 5 * $_[0] },
1245     on_changed => sub {
1246     my ($slider, $value) = @_;
1247    
1248     $::CFG->{pickup} &= ~0x7;
1249     $::CFG->{pickup} |= int $value
1250     if $value;
1251     1;
1252     });
1253     $table->add (3, 18, new CFClient::UI::Button
1254     text => "set",
1255     on_activate => sub {
1256     $::CONN->send_command ("pickup $::CFG->{pickup}")
1257     if defined $::CONN;
1258     1;
1259     });
1260    
1261 root 1.51 $table
1262 elmex 1.43 }
1263    
1264 root 1.60 sub inventory_window {
1265 root 1.23 my $invwin = $INV_WINDOW = new CFClient::UI::FancyFrame
1266 root 1.32 x => "center",
1267     y => "center",
1268     force_w => $WIDTH * 9/10,
1269     force_h => $HEIGHT * 9/10,
1270     title => "Inventory",
1271 root 1.21 ;
1272 root 1.1
1273 root 1.21 $invwin->add (my $hb = new CFClient::UI::HBox homogeneous => 1);
1274 root 1.1
1275 root 1.21 $hb->add (my $vb1 = new CFClient::UI::VBox);
1276     $vb1->add (new CFClient::UI::Label align => 0, text => "Player");
1277     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
1278 root 1.1
1279 root 1.21 $hb->add (my $vb2 = new CFClient::UI::VBox);
1280 elmex 1.17
1281 elmex 1.27 $vb2->add ($INV_RIGHT_HB = new CFClient::UI::HBox);
1282 elmex 1.14
1283 root 1.1 $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
1284    
1285 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1286     CFClient::Protocol::set_opencont ($::CONN, 0, "Floor");
1287    
1288 root 1.1 $invwin
1289     }
1290    
1291 root 1.49 sub spell_setup {
1292     new CFClient::UI::SpellList
1293 elmex 1.38 }
1294    
1295 root 1.49 sub keyboard_setup {
1296 elmex 1.24 my $binding_list = new CFClient::UI::VBox;
1297    
1298 elmex 1.34 my $refresh;
1299     $refresh = sub {
1300 elmex 1.24 $binding_list->clear ();
1301    
1302     for my $mod (keys %{$::CFG->{bindings}}) {
1303     for my $sym (keys %{$::CFG->{bindings}->{$mod}}) {
1304     my $cmds = $::CFG->{bindings}->{$mod}->{$sym};
1305     next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1306    
1307     my $lbl = join "; ", @$cmds;
1308 elmex 1.34 my $nam = CFClient::Binder::keycombo_to_name ($mod, $sym);
1309 elmex 1.24 $binding_list->add (my $hb = new CFClient::UI::HBox);
1310     $hb->add (new CFClient::UI::Button
1311 elmex 1.25 text => "delete",
1312 elmex 1.34 tooltip => "Deletes the binding",
1313 elmex 1.24 on_activate => sub {
1314     $binding_list->remove ($hb);
1315     delete $::CFG->{bindings}->{$mod}->{$sym};
1316     });
1317 elmex 1.34
1318     $hb->add (new CFClient::UI::Button
1319     text => "edit",
1320     tooltip => "Edits the binding",
1321     on_activate => sub {
1322     $::BIND_EDITOR->set_binding (
1323     $mod, $sym, $::CFG->{bindings}->{$mod}->{$sym},
1324     sub {
1325     my ($nmod, $nsym, $ncmds) = @_;
1326     delete $::CFG->{bindings}->{$mod}->{$sym};
1327     $::CFG->{bindings}->{$nmod}->{$nsym} = $ncmds;
1328     $refresh->();
1329 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1330     $SETUP_DIALOG->show;
1331 elmex 1.34 },
1332     sub {
1333 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1334     $SETUP_DIALOG->show;
1335 elmex 1.34 });
1336     $::BIND_EDITOR->show;
1337 root 1.49 $SETUP_DIALOG->hide;
1338 elmex 1.34 });
1339    
1340     $hb->add (new CFClient::UI::Label text => "(Key: $nam)");
1341 elmex 1.24 $hb->add (new CFClient::UI::Label text => $lbl, expand => 1);
1342     }
1343     }
1344     };
1345    
1346 root 1.49 my $vb = new CFClient::UI::VBox;
1347 elmex 1.35 $vb->add ($binding_list);
1348     $vb->add (my $hb = new CFClient::UI::HBox);
1349 root 1.49
1350 elmex 1.35 $hb->add (new CFClient::UI::Button
1351 elmex 1.34 text => "record new",
1352 elmex 1.35 expand => 1,
1353 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1354     on_activate => sub {
1355     $::BIND_EDITOR->set_binding (undef, undef, [],
1356     sub {
1357     my ($mod, $sym, $cmds) = @_;
1358     $::CFG->{bindings}->{$mod}->{$sym} = $cmds;
1359     $refresh->();
1360 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1361     $SETUP_DIALOG->show;
1362 elmex 1.34 },
1363     sub {
1364 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1365     $SETUP_DIALOG->show;
1366 root 1.53 },
1367     );
1368 root 1.49 $SETUP_DIALOG->hide;
1369 elmex 1.34 $::BIND_EDITOR->show;
1370     },
1371     );
1372 root 1.49
1373 elmex 1.35 $hb->add (new CFClient::UI::Button
1374     text => "close",
1375     tooltip => "Closes the binding window",
1376     expand => 1,
1377     on_activate => sub {
1378 root 1.49 $SETUP_DIALOG->hide;
1379 elmex 1.35 }
1380     );
1381    
1382 elmex 1.24 $refresh->();
1383 root 1.49
1384     $vb
1385 elmex 1.24 }
1386    
1387 root 1.64 sub help_window {
1388 root 1.1 my $win = new CFClient::UI::FancyFrame
1389 root 1.41 x => 'center',
1390     y => 'center',
1391 root 1.55 z => 2,
1392 root 1.41 name => 'doc_browser',
1393     force_w => int $WIDTH * 7/8,
1394     force_h => int $HEIGHT * 7/8,
1395     title => "Documentation";
1396 root 1.1
1397     $win->add (my $vbox = new CFClient::UI::VBox);
1398    
1399     $vbox->add (my $buttons = new CFClient::UI::HBox);
1400 root 1.64 $vbox->add (my $viewer = new CFClient::UI::TextScroller
1401     expand => 1, fontsize => 0.8, padding_x => 4);
1402 root 1.1
1403 root 1.64 $buttons->add (new CFClient::UI::Label text => "Choose a document to display: ");
1404     $buttons->add (my $combo = new CFClient::UI::Combobox
1405     value => undef,
1406     options => [
1407     [intro => "Introduction"],
1408     [manual => "Manual"],
1409     [skill_help => "Skills"],
1410     [command_help => "Commands"],
1411     [dmcommand_help => "DM Commands"],
1412     [COPYING => "License Terms"],
1413     ],
1414     on_changed => sub {
1415     my ($self, $pod) = @_;
1416 root 1.1
1417 root 1.64 my $pom = CFClient::load_pod CFClient::find_rcfile "pod/$pod.pod",
1418     doc_viewer => 1, sub { CFClient::pod_to_pango_list $_[0] };
1419 root 1.1
1420 root 1.64 $viewer->clear;
1421    
1422     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
1423     for @$pom;
1424 root 1.1
1425 root 1.64 $viewer->set_offset (0);
1426     },
1427     on_visibility_change => sub {
1428     my ($self, $visible) = @_;
1429     return unless $visible;
1430     return if $self->{value};
1431     $self->set_value ("intro");
1432     },
1433     );
1434 root 1.1
1435     $win
1436     }
1437    
1438     sub sdl_init {
1439     CFClient::SDL_Init
1440     and die "SDL::Init failed!\n";
1441     }
1442    
1443     sub video_init {
1444     sdl_init;
1445    
1446     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1447    
1448     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1449    
1450     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1451     $FULLSCREEN = $CFG->{fullscreen};
1452     $FAST = $CFG->{fast};
1453    
1454     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1455     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1456    
1457     $SDL_ACTIVE = 1;
1458     $LAST_REFRESH = time - 0.01;
1459    
1460 root 1.10 CFClient::OpenGL::init;
1461 root 1.1
1462     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1463    
1464     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1465    
1466     #############################################################################
1467    
1468     if ($DEBUG_STATUS) {
1469     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1470     } else {
1471     # create the widgets
1472    
1473 root 1.30 $DEBUG_STATUS = new CFClient::UI::Label
1474     padding => 0,
1475     z => 100,
1476     force_x => "max",
1477     force_y => 0;
1478 root 1.1 $DEBUG_STATUS->show;
1479 elmex 1.34
1480     $BIND_EDITOR = new CFClient::UI::BindEditor (x => "max", y => 0);
1481    
1482 root 1.1 $STATUSBOX = new CFClient::UI::Statusbox;
1483 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1484 root 1.1
1485     (new CFClient::UI::Frame
1486     bg => [0, 0, 0, 0.4],
1487 root 1.30 force_x => 0,
1488     force_y => "max",
1489 root 1.1 child => $STATUSBOX,
1490     )->show;
1491    
1492     CFClient::UI::FancyFrame->new (
1493 root 1.47 title => "Map",
1494 root 1.42 name => "mapmap",
1495 root 1.30 x => 0,
1496     y => $FONTSIZE + 8,
1497 root 1.1 border_bg => [1, 1, 1, 192/255],
1498     bg => [1, 1, 1, 0],
1499     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1500     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1501     ),
1502     )->show;
1503    
1504     $MAPWIDGET = new CFClient::MapWidget;
1505     $MAPWIDGET->connect (activate_console => sub {
1506     my ($mapwidget, $preset) = @_;
1507    
1508     if ($CONSOLE) {
1509     $CONSOLE->{input}->{auto_activated} = 1;
1510     $CONSOLE->{input}->focus_in;
1511    
1512     if ($preset && $CONSOLE->{input}->get_text eq '') {
1513     $CONSOLE->{input}->set_text ($preset);
1514     }
1515     }
1516     });
1517     $MAPWIDGET->show;
1518     $MAPWIDGET->focus_in;
1519    
1520 root 1.64 $LOGVIEW = new CFClient::UI::TextScroller
1521 root 1.1 expand => 1,
1522     font => $FONT_FIXED,
1523     fontsize => $::CFG->{log_fontsize},
1524 root 1.61 indent => -4,
1525 root 1.1 can_hover => 1,
1526     can_events => 1,
1527     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1528     ;
1529    
1530 root 1.49 $SETUP_DIALOG = new CFClient::UI::FancyFrame
1531     title => "Setup",
1532     name => "setup_dialog",
1533     x => 'center',
1534     y => 'center',
1535 root 1.53 z => 2,
1536 root 1.49 force_w => $::WIDTH * 0.6,
1537     force_h => $::HEIGHT * 0.6,
1538     ;
1539    
1540 root 1.50 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFClient::UI::Notebook expand => 1, debug => 1,
1541 root 1.60 filter => new CFClient::UI::ScrolledWindow expand => 1, scroll_y => 1);
1542 root 1.49
1543     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1544     "Configure the server to play on, your username, password and other server-related options.");
1545     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1546 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1547 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1548     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1549     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1550     "Configure the use of audio, sound effects and background music.");
1551     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1552     "Lets you define, edit and delete bindings."
1553 root 1.54 . "There is a shortcut for making bindings: <b>Left Control + Insert</b> opens the binding editor "
1554 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1555 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1556 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1557     . "binding editor closes");
1558     $SETUP_NOTEBOOK->add (Spells => $SETUP_SPELLS = spell_setup,
1559     "Displays all spells you have and lets you edit keyboard shortcuts for them.");
1560 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1561     "Some debuggign options. Do not ask.");
1562 root 1.49
1563 root 1.57 $BUTTONBAR = new CFClient::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1564 root 1.1
1565 root 1.49 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1566     tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1567    
1568 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1569 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1570    
1571     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
1572    
1573 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => $STATS_WINDOW = stats_window,
1574 root 1.1 tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1575 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => inventory_window,
1576 root 1.51 tooltip => "Toggles the inventory window, where you can manage your loot (or treasures :). "
1577 root 1.52 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory.");
1578 root 1.1
1579     $BUTTONBAR->add (new CFClient::UI::Button
1580     text => "Save Config",
1581     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1582 root 1.18 on_activate => sub {
1583 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1584 root 1.28 CFClient::write_cfg "$Crossfire::VARDIR/cfplusrc";
1585 root 1.1 status "Configuration Saved";
1586     },
1587     );
1588    
1589 root 1.64 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => help_window,
1590 root 1.1 tooltip => "View Documentation");
1591    
1592     $BUTTONBAR->add (new CFClient::UI::Button
1593 root 1.18 text => "Quit",
1594     tooltip => "Terminates the program",
1595     on_activate => sub {
1596 root 1.1 if ($CONN) {
1597     open_quit_dialog;
1598     } else {
1599     exit;
1600     }
1601     },
1602     );
1603    
1604     $BUTTONBAR->show;
1605 root 1.49 $SETUP_DIALOG->show;
1606     }
1607 root 1.1
1608 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1609 root 1.1 }
1610    
1611     sub video_shutdown {
1612     undef $SDL_ACTIVE;
1613     }
1614    
1615     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1616     my $bgmusic;#TODO#hack#d#
1617    
1618     sub audio_channel_finished {
1619     my ($channel) = @_;
1620    
1621     #warn "channel $channel finished\n";#d#
1622     }
1623    
1624     sub audio_music_finished {
1625     return unless $CFG->{bgm_enable};
1626    
1627     # TODO: hack, do play loop and mood music
1628     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1629     $bgmusic->play (0);
1630    
1631     push @bgmusic, shift @bgmusic;
1632     }
1633    
1634     sub audio_init {
1635     if ($CFG->{audio_enable}) {
1636     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1637     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1638    
1639     unless ($SDL_MIXER) {
1640     status "Unable to open sound device: there will be no sound";
1641     return;
1642     }
1643    
1644     CFClient::Mix_AllocateChannels 8;
1645     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1646    
1647     audio_music_finished;
1648    
1649     while (<$fh>) {
1650     next if /^\s*#/;
1651     next if /^\s*$/;
1652    
1653     my ($file, $volume, $event) = split /\s+/, $_, 3;
1654    
1655     push @SOUNDS, "$volume,$file";
1656    
1657     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1658     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1659     $chunk->volume ($volume * 128 / 100);
1660     $chunk
1661     };
1662     }
1663     } else {
1664     status "unable to open sound config: $!";
1665     }
1666     }
1667     }
1668    
1669     sub audio_shutdown {
1670     CFClient::Mix_CloseAudio if $SDL_MIXER;
1671     undef $SDL_MIXER;
1672     @SOUNDS = ();
1673     %AUDIO_CHUNKS = ();
1674     }
1675    
1676     my %animate_object;
1677     my $animate_timer;
1678    
1679     my $fps = 9;
1680    
1681     my %demo;#d#
1682    
1683     sub force_refresh {
1684     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1685 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1686 root 1.1
1687     $CFClient::UI::ROOT->draw;
1688    
1689     $WANT_REFRESH = 0;
1690     $CAN_REFRESH = 0;
1691     $LAST_REFRESH = $NOW;
1692    
1693     0 && do {
1694     # some weird model-drawing code, just a joke right now
1695     use CFClient::OpenGL;
1696    
1697     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1698     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1699     $demo{r} ||= do {
1700     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1701     $mod->{v} = pack "f*", @{$mod->{v}};
1702     $_ = [scalar @$_, pack "S!*", @$_]
1703     for values %{$mod->{g}};
1704     $mod
1705     };
1706    
1707     my $r = $demo{r} or die;
1708    
1709     glDepthMask 1;
1710     glClear GL_DEPTH_BUFFER_BIT;
1711     glEnable GL_TEXTURE_2D;
1712     glEnable GL_DEPTH_TEST;
1713     glEnable GL_CULL_FACE;
1714     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1715    
1716     glMatrixMode GL_PROJECTION;
1717     glLoadIdentity;
1718     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1719     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1720     glMatrixMode GL_MODELVIEW;
1721     glLoadIdentity;
1722    
1723     glPushMatrix;
1724     glTranslate 0, 0, -800;
1725     glScale 1, -1, 1;
1726     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1727     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1728     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1729     glScale 50, 50, 50;
1730    
1731     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1732     while (my ($k, $v) = each %{$r->{g}}) {
1733     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1734     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1735     }
1736    
1737     glPopMatrix;
1738    
1739     glShadeModel GL_FLAT;
1740     glDisable GL_DEPTH_TEST;
1741     glDisable GL_TEXTURE_2D;
1742     glDepthMask 0;
1743    
1744     $WANT_REFRESH++;
1745     };
1746    
1747     CFClient::SDL_GL_SwapBuffers;
1748     }
1749    
1750 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1751 root 1.1 $NOW = time;
1752    
1753     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1754     for CFClient::SDL_PollEvent;
1755    
1756     if (%animate_object) {
1757     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1758     $WANT_REFRESH++;
1759     }
1760    
1761     if ($WANT_REFRESH) {
1762     force_refresh;
1763     } else {
1764     $CAN_REFRESH = 1;
1765     }
1766     });
1767    
1768     sub animation_start {
1769     my ($widget) = @_;
1770     $animate_object{$widget} = $widget;
1771     }
1772    
1773     sub animation_stop {
1774     my ($widget) = @_;
1775     delete $animate_object{$widget};
1776     }
1777    
1778     # check once/second for faces that need to be prefetched
1779     # this should, of course, only run on demand, but
1780     # SDL forces worse things on us....
1781    
1782     Event->timer (after => 1, interval => 0.25, cb => sub {
1783     $CONN->face_prefetch
1784     if $CONN;
1785     });
1786    
1787     %SDL_CB = (
1788     CFClient::SDL_QUIT => sub {
1789     Event::unloop -1;
1790     },
1791     CFClient::SDL_VIDEORESIZE => sub {
1792     },
1793     CFClient::SDL_VIDEOEXPOSE => sub {
1794     CFClient::UI::full_refresh;
1795     },
1796     CFClient::SDL_ACTIVEEVENT => sub {
1797     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1798     },
1799     CFClient::SDL_KEYDOWN => sub {
1800     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1801     # alt-enter
1802     video_shutdown;
1803     $CFG->{fullscreen} = !$CFG->{fullscreen};
1804     video_init;
1805     } else {
1806     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1807     }
1808     },
1809     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1810     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1811     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1812     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1813     CFClient::SDL_USEREVENT => sub {
1814     if ($_[0]{code} == 1) {
1815     audio_channel_finished $_[0]{data1};
1816     } elsif ($_[0]{code} == 0) {
1817     audio_music_finished;
1818     }
1819     },
1820     );
1821    
1822     #############################################################################
1823    
1824     $SIG{INT} = $SIG{TERM} = sub { exit };
1825    
1826     {
1827 root 1.49 local $SIG{__DIE__} = sub {
1828     return unless defined $^S && !$^S;
1829     Carp::confess $_[1];#d#TODO: remove when stable
1830     CFClient::fatal $_[0];
1831     };
1832 root 1.1
1833 root 1.28 CFClient::read_cfg "$Crossfire::VARDIR/cfplusrc";
1834 elmex 1.16 CFClient::UI::set_layout ($::CFG->{layout});
1835 root 1.1
1836     my %DEF_CFG = (
1837     sdl_mode => 0,
1838     width => 640,
1839     height => 480,
1840     fullscreen => 0,
1841     fast => 0,
1842     map_scale => 1,
1843     fow_enable => 1,
1844     fow_intensity => 0.45,
1845     fow_smooth => 0,
1846     gui_fontsize => 1,
1847 root 1.60 log_fontsize => 0.7,
1848 root 1.1 gauge_fontsize=> 1,
1849     gauge_size => 0.35,
1850 root 1.60 stat_fontsize => 0.7,
1851 root 1.1 mapsize => 100,
1852     host => "crossfire.schmorp.de",
1853     say_command => 'say',
1854     audio_enable => 1,
1855     bgm_enable => 1,
1856     bgm_volume => 0.25,
1857     face_prefetch => 0,
1858     output_sync => 1,
1859     output_count => 1,
1860 root 1.63 pickup => 0,
1861 root 1.1 );
1862    
1863     while (my ($k, $v) = each %DEF_CFG) {
1864     $CFG->{$k} = $v unless exists $CFG->{$k};
1865     }
1866    
1867     sdl_init;
1868    
1869     @SDL_MODES = reverse
1870     grep $_->[0] >= 640 && $_->[1] >= 480,
1871     CFClient::SDL_ListModes;
1872    
1873     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1874    
1875     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1876    
1877     {
1878     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1879     DejaVuSans.ttf
1880     DejaVuSansMono.ttf
1881     DejaVuSans-Bold.ttf
1882     DejaVuSansMono-Bold.ttf
1883     DejaVuSans-Oblique.ttf
1884     DejaVuSansMono-Oblique.ttf
1885     DejaVuSans-BoldOblique.ttf
1886     DejaVuSansMono-BoldOblique.ttf
1887     );
1888    
1889     CFClient::add_font $_ for @fonts;
1890    
1891     CFClient::pango_init;
1892    
1893     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1894     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1895    
1896     $FONT_PROP->make_default;
1897     }
1898    
1899     # compare mono (ft) vs. rgba (cairo)
1900     # ft - 1.8s, cairo 3s, even in alpha-only mode
1901     # for my $rgba (0..1) {
1902     # my $t1 = Time::HiRes::time;
1903     # for (1..1000) {
1904     # my $layout = CFClient::Layout->new ($rgba);
1905     # $layout->set_text ("hallo" x 100);
1906     # $layout->render;
1907     # }
1908     # my $t2 = Time::HiRes::time;
1909     # warn $t2-$t1;
1910     # }
1911    
1912     video_init;
1913     audio_init;
1914     }
1915    
1916     Event::loop;
1917    
1918     END { CFClient::SDL_Quit }
1919    
1920     =head1 NAME
1921    
1922 root 1.28 cfplus - A Crossfire+ and Crossfire game client
1923 root 1.1
1924     =head1 SYNOPSIS
1925    
1926     Just run it - no commandline arguments are supported.
1927    
1928     =head1 USAGE
1929    
1930 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
1931 root 1.1 fullscreen and interactively.
1932    
1933 root 1.39 =head1 DEBUGGING
1934    
1935    
1936     CFPLUS_DEBUG - environment variable
1937    
1938     1 draw borders around widgets
1939     2 add low-level widget info to tooltips
1940     4 show fps
1941     8 suppress tooltips
1942    
1943 root 1.1 =head1 AUTHOR
1944    
1945     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1946    
1947    
1948