ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.92
Committed: Fri Jul 14 17:35:35 2006 UTC (17 years, 10 months ago) by elmex
Branch: MAIN
Changes since 1.91: +58 -27 lines
Log Message:
some minor enhancements on the playerbook and the inventory.

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