ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.94
Committed: Sun Jul 16 20:04:08 2006 UTC (17 years, 10 months ago) by root
Branch: MAIN
Changes since 1.93: +3 -2 lines
Log Message:
*** empty log message ***

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.94 our $FULLSCREEN_ENABLE;
92 root 1.86 our $PICKUP_ENABLE;
93 root 1.67 our $SERVER_INFO;
94 root 1.49
95     our $SETUP_DIALOG;
96     our $SETUP_NOTEBOOK;
97     our $SETUP_SERVER;
98     our $SETUP_KEYBOARD;
99 root 1.1
100 root 1.86 our $PL_NOTEBOOK;
101     our $PL_WINDOW;
102    
103     our $INVENTORY_PAGE;
104     our $STATS_PAGE;
105     our $SPELL_PAGE;
106    
107     our $HELP_WINDOW;
108 root 1.60 our $MESSAGE_WINDOW;
109 root 1.1 our $FLOORBOX;
110     our $GAUGES;
111     our $STATWIDS;
112    
113     our $SDL_ACTIVE;
114     our %SDL_CB;
115    
116     our $SDL_MIXER;
117     our @SOUNDS; # event => file mapping
118     our %AUDIO_CHUNKS; # audio files
119    
120     our $ALT_ENTER_MESSAGE;
121     our $STATUSBOX;
122     our $DEBUG_STATUS;
123    
124     our $INV;
125     our $INVR;
126 elmex 1.27 our $INV_RIGHT_HB;
127 root 1.1
128 elmex 1.34 our $BIND_EDITOR;
129 elmex 1.77 our $BIND_UPD_CB;
130 elmex 1.24
131 elmex 1.43 our $PICKUP_CFG;
132 elmex 1.38
133 root 1.1 sub status {
134     $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
135     }
136    
137     sub debug {
138     $DEBUG_STATUS->set_text ($_[0]);
139     }
140    
141 root 1.60 sub destroy_query_dialog {
142     (delete $_[0]{query_dialog})->destroy
143     if $_[0]{query_dialog};
144     }
145    
146     # server query dialog
147     sub server_query {
148     my ($conn, $flags, $prompt) = @_;
149    
150     $conn->{query_dialog} = my $dialog = new CFClient::UI::FancyFrame
151     x => "center",
152     y => "center",
153     title => "Server Query",
154     child => my $vbox = new CFClient::UI::VBox,
155     ;
156    
157     my @dialog = my $label = new CFClient::UI::Label
158     max_w => $::WIDTH * 0.4,
159     ellipsise => 0,
160     text => $prompt;
161    
162     if ($flags & CS_QUERY_YESNO) {
163     push @dialog, my $hbox = new CFClient::UI::HBox;
164    
165     $hbox->add (new CFClient::UI::Button
166     text => "No",
167     on_activate => sub {
168     $conn->send ("reply n");
169     $dialog->destroy;
170 root 1.74 0
171 root 1.60 }
172     );
173     $hbox->add (new CFClient::UI::Button
174     text => "Yes",
175     on_activate => sub {
176     $conn->send ("reply y");
177     destroy_query_dialog $conn;
178 root 1.74 0
179 root 1.60 },
180     );
181    
182 root 1.74 $dialog->grab_focus;
183 root 1.60
184     } elsif ($flags & CS_QUERY_SINGLECHAR) {
185     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
186    
187     if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
188     $MESSAGE_WINDOW->show;
189    
190     unshift @dialog, new CFClient::UI::Label
191     max_w => $::WIDTH * 0.4,
192     ellipsise => 0,
193     markup => "\nOr use your keyboard:\n";
194    
195     unshift @dialog, my $table = new CFClient::UI::Table;
196    
197     $table->add (0, 0, new CFClient::UI::Button
198     text => "Next Race",
199     on_activate => sub {
200     $conn->send ("reply n");
201     destroy_query_dialog $conn;
202 root 1.74 0
203 root 1.60 },
204     );
205     $table->add (2, 0, new CFClient::UI::Button
206     text => "Accept",
207     on_activate => sub {
208     $conn->send ("reply d");
209     destroy_query_dialog $conn;
210 root 1.74 0
211 root 1.60 },
212     );
213    
214     unshift @dialog, new CFClient::UI::Label
215     max_w => $::WIDTH * 0.4,
216     ellipsise => 0,
217     markup =>
218     "<big><b>Character Creation: Race</b></big>\n\n"
219     . "Look at the <b>Messages</b> window to see a description of this race "
220     . "(<small>or hover with your mouse over the bottommost entry in the status area in the lower left area of the screen</small>) "
221     . "and the center of the screen to see how this race looks like "
222     . "(<small>this is below this dialog window, you may need to click on the display area to make it visible</small>).\n\n"
223     . "You can look at another race, or accept this race (you will come back to this race eventually, "
224     . "so you can take your time making this important choice."
225     ;
226    
227     } elsif ($prompt =~ /roll new stats/) {
228     if (my $stat = delete $conn->{stat_change_with}) {
229     $conn->send ("reply $stat");
230     destroy_query_dialog $conn;
231     return;
232     }
233    
234 root 1.86 $STATS_PAGE->show;
235 root 1.60 $MESSAGE_WINDOW->hide;
236    
237     unshift @dialog, new CFClient::UI::Label
238     max_w => $::WIDTH * 0.4,
239     ellipsise => 0,
240     markup => "\nOr use your keyboard:\n";
241    
242     unshift @dialog, my $table = new CFClient::UI::Table;
243    
244     # left: re-roll
245     $table->add (0, 0, new CFClient::UI::Button
246     text => "Roll Again",
247     on_activate => sub {
248     $conn->send ("reply y");
249     destroy_query_dialog $conn;
250 root 1.74 0
251 root 1.60 },
252     );
253    
254     # center: swap stats
255     my ($sw1, $sw2) = map +(new CFClient::UI::Combobox
256     value => $_,
257     options => [
258 root 1.64 [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
259     [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
260     [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
261     [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
262     [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
263     [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
264     [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
265 root 1.60 ],
266     ), 1 .. 2;
267    
268     $table->add (2, 0, new CFClient::UI::Button
269     text => "Swap Stats",
270     on_activate => sub {
271     $conn->{stat_change_with} = $sw2->{value};
272     $conn->send ("reply $sw1->{value}");
273     destroy_query_dialog $conn;
274 root 1.74 0
275 root 1.60 },
276     );
277     $table->add (2, 1, new CFClient::UI::HBox children => [$sw1, $sw2]);
278    
279     # right: accept
280     $table->add (4, 0, new CFClient::UI::Button
281     text => "Accept",
282     on_activate => sub {
283     $conn->send ("reply n");
284 root 1.86 $STATS_PAGE->hide;
285 root 1.60 destroy_query_dialog $conn;
286 root 1.74 0
287 root 1.60 },
288     );
289    
290     unshift @dialog, new CFClient::UI::Label
291     max_w => $::WIDTH * 0.4,
292     ellipsise => 0,
293     markup =>
294     "<big><b>Character Creation: Stats</b></big>\n\n"
295     . "Look at the <b>Stats</b> window to see your basic stats "
296     . "(first column: 1 strength, 2 dexterity, 3 constitution, 4 intelligence, 5 wisdom, 6 power and 7 charisma).\n\n"
297     . "You can create another set of stats, swap two stat values with each other or accept the stats as they are now and continue. "
298     . "Race selection will influence those values later on."
299     ;
300     }
301    
302     push @dialog, my $entry = new CFClient::UI::Entry
303     on_changed => sub {
304     $conn->send ("reply $_[1]");
305     destroy_query_dialog $conn;
306 root 1.74 0
307 root 1.60 },
308     ;
309    
310 root 1.74 $entry->grab_focus;
311 root 1.60
312     } else {
313     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
314    
315     push @dialog, my $entry = new CFClient::UI::Entry
316     $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
317     on_activate => sub {
318     $conn->send ("reply $_[1]");
319     destroy_query_dialog $conn;
320 root 1.74 0
321 root 1.60 },
322     ;
323    
324 root 1.74 $entry->grab_focus;
325 root 1.60 }
326    
327     $vbox->add (@dialog);
328     $dialog->show;
329     }
330    
331 root 1.1 sub start_game {
332     status "logging in...";
333    
334 root 1.23 $LOGIN_BUTTON->set_text ("Logout");
335 root 1.49 $SETUP_DIALOG->hide;
336 root 1.23
337 root 1.1 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
338    
339 root 1.75 my ($host, $port) = split /:/, $CFG->{profile}{default}{host};
340 root 1.11
341 root 1.1 $MAP = new CFClient::Map $mapsize, $mapsize;
342    
343     $CONN = eval {
344 root 1.11 new CFClient::Protocol
345 root 1.1 host => $host,
346     port => $port || 13327,
347 root 1.75 user => $CFG->{profile}{default}{user},
348     pass => $CFG->{profile}{default}{password},
349 root 1.1 mapw => $mapsize,
350     maph => $mapsize,
351 root 1.11
352     map_widget => $MAPWIDGET,
353     logview => $LOGVIEW,
354     statusbox => $STATUSBOX,
355     map => $MAP,
356     mapmap => $MAPMAP,
357 root 1.60 query => \&server_query,
358 root 1.11
359     sound_play => sub {
360     my ($x, $y, $soundnum, $type) = @_;
361    
362     $SDL_MIXER
363     or return;
364    
365     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
366     or return;
367    
368     $chunk->play;
369     },
370 root 1.1 };
371    
372     if ($CONN) {
373     CFClient::lowdelay fileno $CONN->{fh};
374    
375     status "login successful";
376     } else {
377     status "unable to connect";
378     stop_game();
379     }
380     }
381    
382     sub stop_game {
383 root 1.23 $LOGIN_BUTTON->set_text ("Login");
384 root 1.53 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
385 root 1.49 $SETUP_DIALOG->show;
386 elmex 1.85 $PL_WINDOW->hide;
387 root 1.86 $SPELL_PAGE->clear_spells;
388 root 1.23
389 root 1.1 return unless $CONN;
390    
391     status "connection closed";
392 root 1.23
393 root 1.60 destroy_query_dialog $CONN;
394 root 1.1 $CONN->destroy;
395     $CONN = 0; # false, does not autovivify
396 root 1.76
397     undef $MAP;
398 root 1.1 }
399    
400 root 1.49 sub graphics_setup {
401     my $vbox = new CFClient::UI::VBox;
402 root 1.30
403 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
404    
405     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
406     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
407    
408 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]);
409 root 1.1 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
410    
411     $mode_slider->connect (changed => sub {
412     my ($self, $value) = @_;
413    
414     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
415     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
416     });
417     $mode_slider->emit (changed => $mode_slider->{range}[0]);
418    
419     my $row = 1;
420    
421     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
422 root 1.94 $table->add (1, $row++, $FULLSCREEN_ENABLE = new CFClient::UI::CheckBox
423 root 1.1 state => $CFG->{fullscreen},
424     tooltip => "Bring the client into fullscreen mode.",
425 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
426 root 1.1 );
427    
428     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
429     $table->add (1, $row++, new CFClient::UI::CheckBox
430     state => $CFG->{fast},
431     tooltip => "Lower the visual quality considerably to speed up rendering.",
432 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
433 root 1.1 );
434    
435     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
436     $table->add (1, $row++, new CFClient::UI::Slider
437     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
438     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
439 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
440 root 1.1 );
441    
442     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
443     $table->add (1, $row++, new CFClient::UI::CheckBox
444     state => $CFG->{fow_enable},
445     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
446 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
447 root 1.1 );
448    
449     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
450     $table->add (1, $row++, new CFClient::UI::Slider
451     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
452     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
453 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
454 root 1.1 );
455    
456     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
457     $table->add (1, $row++, new CFClient::UI::CheckBox
458     state => $CFG->{fow_smooth},
459     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
460 root 1.18 on_changed => sub {
461 root 1.1 my ($self, $value) = @_;
462     $CFG->{fow_smooth} = $value;
463 root 1.15 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::OpenGL::GL_VERSION < 1.2;
464 root 1.74 0
465 root 1.1 }
466     );
467    
468     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
469     $table->add (1, $row++, new CFClient::UI::Slider
470     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
471     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
472 root 1.74 on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
473 root 1.1 );
474    
475     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
476     $table->add (1, $row++, new CFClient::UI::Slider
477     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
478     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
479 root 1.74 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
480 root 1.1 );
481    
482     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
483     $table->add (1, $row++, new CFClient::UI::Slider
484     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
485     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
486 root 1.18 on_changed => sub {
487 root 1.1 $CFG->{gauge_fontsize} = $_[1];
488     &set_gauge_window_fontsize;
489 root 1.74 0
490 root 1.1 }
491     );
492    
493     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
494     $table->add (1, $row++, new CFClient::UI::Slider
495 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
496     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
497     on_changed => sub {
498 root 1.1 $CFG->{gauge_size} = $_[1];
499     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
500 root 1.74 0
501 root 1.1 }
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 root 1.74 0
511 root 1.1 }
512     );
513    
514 root 1.49 $vbox
515     }
516    
517     sub audio_setup {
518     my $vbox = new CFClient::UI::VBox;
519    
520     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
521    
522     my $row = 0;
523    
524 root 1.1 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
525     $table->add (1, $row++, new CFClient::UI::CheckBox
526     state => $CFG->{audio_enable},
527     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.",
528 root 1.74 on_changed => sub { $CFG->{audio_enable} = $_[1]; 0 }
529 root 1.1 );
530     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
531 root 1.18 # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
532 root 1.1 # $CFG->{effects_volume} = $_[1];
533     # });
534     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
535     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
536     $hbox->add (new CFClient::UI::CheckBox
537     expand => 1, state => $CFG->{bgm_enable},
538     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
539 root 1.74 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 }
540 root 1.1 );
541     $hbox->add (new CFClient::UI::Slider
542     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
543     tooltip => "The volume of the background music. Changes are instant.",
544 root 1.74 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFClient::MixMusic::volume $_[1] * 128; 0 }
545 root 1.1 );
546    
547     $table->add (1, $row++, new CFClient::UI::Button
548     expand => 1, align => 0, text => "Apply",
549     tooltip => "Apply the audio settings",
550 root 1.18 on_activate => sub {
551 root 1.1 audio_shutdown ();
552     audio_init ();
553 root 1.74 0
554 root 1.1 }
555     );
556    
557 root 1.49 $vbox
558 root 1.1 }
559    
560     sub set_gauge_window_fontsize {
561     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
562     $_->set_fontsize ($::CFG->{gauge_fontsize});
563     }
564     }
565    
566     sub make_gauge_window {
567     my $gh = int $HEIGHT * $CFG->{gauge_size};
568    
569     my $win = new CFClient::UI::Frame (
570 root 1.30 force_x => 0,
571     force_y => "max",
572     force_w => $WIDTH,
573     force_h => $gh,
574 root 1.1 );
575    
576     $win->add (my $hbox = new CFClient::UI::HBox
577     children => [
578     (new CFClient::UI::HBox expand => 1),
579     (new CFClient::UI::VBox children => [
580     (new CFClient::UI::Empty expand => 1),
581 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
582 root 1.1 ]),
583     (my $vbox = new CFClient::UI::VBox),
584     ],
585     );
586    
587     $vbox->add (new CFClient::UI::HBox
588     expand => 1,
589     children => [
590     (new CFClient::UI::Empty expand => 1),
591     (my $hb = new CFClient::UI::HBox),
592     ],
593     );
594    
595     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
596     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.");
597     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
598     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.");
599     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
600     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.");
601     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
602     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.");
603    
604     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
605     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.");
606     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
607     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
608    
609     $GAUGES = {
610     exp => $exp, win => $win, range => $rng,
611     food => $fg, mana => $mg, hp => $hg, grace => $gg
612     };
613    
614     &set_gauge_window_fontsize;
615    
616     $win
617     }
618    
619 root 1.65 sub debug_setup {
620     my $table = new CFClient::UI::Table;
621    
622     $table->add (0, 0, new CFClient::UI::Label text => "Widget Borders");
623 root 1.74 $table->add (1, 0, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
624 root 1.65 $table->add (0, 1, new CFClient::UI::Label text => "Tooltip Widget Info");
625 root 1.74 $table->add (1, 1, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
626 root 1.65 $table->add (0, 2, new CFClient::UI::Label text => "Show FPS");
627 root 1.74 $table->add (1, 2, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
628 root 1.65 $table->add (0, 3, new CFClient::UI::Label text => "Suppress Tooltips");
629 root 1.74 $table->add (1, 3, new CFClient::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
630 root 1.65
631     my @default_smooth = (0.05, 0.13, 0.05, 0.13, 0.30, 0.13, 0.05, 0.13, 0.05);
632    
633     for my $x (0..2) {
634     for my $y (0 .. 2) {
635     $table->add ($x + 3, $y,
636     new CFClient::UI::Entry
637     text => $default_smooth[$x * 3 + $y],
638     on_changed => sub { $MAP->{smooth_matrix}[$x * 3 + $y] = $_[1] if $MAP; 0 },
639     );
640     }
641     }
642    
643    
644     $table
645     }
646 elmex 1.24
647 root 1.60 sub stats_window {
648 elmex 1.89 my $vb = new CFClient::UI::VBox;
649 root 1.1
650     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
651     can_hover => 1, can_events => 1,
652     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
653     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
654     can_hover => 1, can_events => 1,
655     tooltip => "The map you are currently on (if supported by the server).");
656    
657 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
658     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
659     can_hover => 1, can_events => 1,
660 root 1.15 tooltip => "The weight of the player including all inventory items.");
661 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
662     can_hover => 1, can_events => 1,
663 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
664 elmex 1.5
665    
666 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
667     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
668    
669     my $color2 = [1, 1, 0];
670    
671     for (
672     [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"],
673     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
674     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
675     [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"],
676     [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"],
677     [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"],
678     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
679    
680     [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."],
681     [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."],
682     [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."],
683 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."],
684 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."],
685     [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."],
686     ) {
687     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
688    
689     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
690     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
691     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
692     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
693     }
694    
695 elmex 1.92 $vb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
696 root 1.1
697     my $row = 0;
698     my $col = 0;
699    
700     my %resist_names = (
701 elmex 1.92 slow => ["Slow",
702     "<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.)"],
703     holyw => ["Holy Word",
704     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
705     conf => ["Confusion",
706     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
707     fire => ["Fire",
708     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
709     depl => ["Depletion",
710     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
711     magic => ["Magic",
712     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
713     drain => ["Draining",
714     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
715     acid => ["Acid",
716     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
717     pois => ["Poison",
718     "<b>Poison</b> (resistance to getting poisoned)"],
719     para => ["Paralysation",
720     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
721     deat => ["Death",
722     "<b>Death</b> (resistance against death spells)"],
723     phys => ["Physical",
724     "<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.)"],
725     blind => ["Blind",
726     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
727     fear => ["Fear",
728     "<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)"],
729     tund => ["Turn undead",
730     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
731     elec => ["Electricity",
732     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
733     cold => ["Cold",
734     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
735     ghit => ["Ghost hit",
736     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
737 root 1.1 );
738     for (qw/slow holyw conf fire depl magic
739     drain acid pois para deat phys
740     blind fear tund elec cold ghit/)
741     {
742     $tbl2->add ($col, $row,
743     $STATWIDS->{"res_$_"} =
744     new CFClient::UI::Label
745     font => $FONT_FIXED,
746     template => "-100%",
747     align => +1,
748     valign => 0,
749     can_events => 1,
750     can_hover => 1,
751 elmex 1.92 tooltip => $resist_names{$_}->[1],
752 root 1.1 );
753     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
754     font => $FONT_FIXED,
755     can_hover => 1,
756     can_events => 1,
757 root 1.78 path => "ui/resist/resist_$_.png",
758 elmex 1.92 tooltip => $resist_names{$_}->[1],
759     );
760     $tbl2->add ($col + 2, $row, new CFClient::UI::Label
761     text => $resist_names{$_}->[0],
762     font => $FONT_FIXED,
763     can_hover => 1,
764     can_events => 1,
765     tooltip => $resist_names{$_}->[1],
766 root 1.1 );
767    
768     $row++;
769     if ($row % 6 == 0) {
770 elmex 1.92 $col += 3;
771 root 1.1 $row = 0;
772     }
773     }
774    
775     update_stats_window ({});
776    
777 elmex 1.89 $vb
778 root 1.1 }
779    
780 elmex 1.92 sub skill_window {
781     my ($self) = @_;
782     $STATWIDS->{"_skill_tbl"} = new CFClient::UI::Table expand => 1;
783     }
784    
785 root 1.48 sub formsep($) {
786     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
787 root 1.1 }
788    
789     sub update_stats_window {
790     my ($stats) = @_;
791    
792 root 1.12 # I love text protocols...
793    
794     my $hp = $stats->{+CS_STAT_HP} * 1;
795     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
796     my $sp = $stats->{+CS_STAT_SP} * 1;
797     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
798     my $fo = $stats->{+CS_STAT_FOOD} * 1;
799 root 1.1 my $fo_m = 999;
800 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
801     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
802 root 1.1
803     $GAUGES->{hp} ->set_value ($hp, $hp_m);
804     $GAUGES->{mana} ->set_value ($sp, $sp_m);
805     $GAUGES->{food} ->set_value ($fo, $fo_m);
806     $GAUGES->{grace} ->set_value ($gr, $gr_m);
807 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
808     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
809     my $rng = $stats->{+CS_STAT_RANGE};
810 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
811     $GAUGES->{range} ->set_text ("Rng: " . $rng);
812 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
813 root 1.1 $title =~ s/^Player: //;
814     $STATWIDS->{title} ->set_text ("Title: " . $title);
815    
816 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
817     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
818     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
819     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
820     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
821     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
822     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
823     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
824     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
825     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
826 elmex 1.82 $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_RES_PHYS});
827 root 1.12 $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
828     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
829 root 1.1
830 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
831 elmex 1.5
832 root 1.1 my %tbl = (
833 elmex 1.72 phys => CS_STAT_RES_PHYS,
834     magic => CS_STAT_RES_MAG,
835     fire => CS_STAT_RES_FIRE,
836     elec => CS_STAT_RES_ELEC,
837     cold => CS_STAT_RES_COLD,
838     conf => CS_STAT_RES_CONF,
839     acid => CS_STAT_RES_ACID,
840     drain => CS_STAT_RES_DRAIN,
841     ghit => CS_STAT_RES_GHOSTHIT,
842     pois => CS_STAT_RES_POISON,
843     slow => CS_STAT_RES_SLOW,
844     para => CS_STAT_RES_PARA,
845     tund => CS_STAT_TURN_UNDEAD,
846     fear => CS_STAT_RES_FEAR,
847     depl => CS_STAT_RES_DEPLETE,
848     deat => CS_STAT_RES_DEATH,
849     holyw => CS_STAT_RES_HOLYWORD,
850     blind => CS_STAT_RES_BLIND,
851 root 1.1 );
852    
853 elmex 1.89 if ($::CONN && !$STATWIDS->{_skill_tbl_init}) {
854     my $sktbl = $STATWIDS->{_skill_tbl};
855     $sktbl->clear;
856    
857     $sktbl->add (0, 0, new CFClient::UI::Label text => "Exp.", align => 1);
858     $sktbl->add (1, 0, new CFClient::UI::Label text => "Level", align => 1);
859     $sktbl->add (2, 0, new CFClient::UI::Label text => "Skillname");
860    
861     my @skills;
862    
863     for (my $i = CS_STAT_SKILLINFO; $i < CS_STAT_SKILLINFO+CS_NUM_SKILLS; $i++) {
864     push @skills, [$i, $::CONN->{skill_info}{$i}];
865     }
866    
867     my $y = 1;
868     for (sort { $a->[1] cmp $b->[1] } @skills) {
869     my ($idx, $name) = @$_;
870    
871     unless (defined $STATWIDS->{"sk_xp_$idx"} || !$::CONN->{skill_info}{$idx}) {
872     $sktbl->add (0, $y, $STATWIDS->{"sk_xp_$idx"} = new CFClient::UI::Label text => "0", align => 1);
873     $sktbl->add (1, $y, $STATWIDS->{"sk_lvl_$idx"} = new CFClient::UI::Label text => "0", align => 1);
874     $sktbl->add (2, $y++, new CFClient::UI::Label text => $name);
875     }
876     }
877    
878     $STATWIDS->{_skill_tbl_init} = 1;
879     }
880    
881     for (my $i = CS_STAT_SKILLINFO; $i < CS_STAT_SKILLINFO+CS_NUM_SKILLS; $i++) {
882     if (exists $stats->{$i}) {
883     $STATWIDS->{"sk_xp_$i"}->set_text (formsep $stats->{$i}->[1])
884     if $STATWIDS->{"sk_xp_$i"};
885     $STATWIDS->{"sk_lvl_$i"}->set_text (sprintf "%d", $stats->{$i}->[0])
886     if $STATWIDS->{"sk_lvl_$i"};
887     }
888     }
889    
890 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
891     for keys %tbl;
892 root 1.1 }
893    
894     my $METASERVER_ATIME;
895    
896     sub update_metaserver {
897 elmex 1.81 my ($metaserver_dialog) = @_;
898    
899     $METASERVER = $metaserver_dialog
900     if defined $metaserver_dialog;
901    
902 root 1.1 return if $METASERVER_ATIME > time;
903     $METASERVER_ATIME = time + 60;
904    
905     my $table = $METASERVER->{table};
906     $table->clear;
907     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
908    
909     my $buf;
910    
911     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
912    
913     unless ($fh) {
914     $label->set_text ("unable to contact metaserver: $!");
915     return;
916     }
917    
918     Event->io (fd => $fh, poll => 'r', cb => sub {
919     my $res = sysread $fh, $buf, 8192, length $buf;
920    
921     if (!defined $res) {
922     $_[0]->w->cancel;
923     $label->set_text ("error while retrieving server list: $!");
924     } elsif ($res == 0) {
925     $_[0]->w->cancel;
926     status "server list retrieved";
927    
928     utf8::decode $buf if utf8::valid $buf;
929    
930     $table->clear;
931    
932 root 1.62 my @tip = (
933     "The current number of users logged in on the server.",
934     "The hostname of the server.",
935     "The time this server has been running without being restarted.",
936     "The server software version - a '+' indicates a Crossfire+ server.",
937     "Short information about this server provided by its admins.",
938     );
939     my @col = qw(#Users Host Uptime Version Description);
940     $table->add ($_, 0, new CFClient::UI::Label
941     can_hover => 1, can_events => 1,
942     align => 0, fg => [1, 1, 0],
943     text => $col[$_], tooltip => $tip[$_])
944     for 0 .. $#col;
945 root 1.1
946     my @align = qw(1 0 1 1 -1);
947    
948     my $y = 0;
949     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
950     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
951    
952     for ($desc) {
953     s/<br>/\n/gi;
954     s/<li>/\n· /gi;
955     s/<.*?>//sgi;
956     s/&/&amp;/g;
957     s/</&lt;/g;
958     s/>/&gt;/g;
959     }
960    
961     $uptime = sprintf "%dd %02d:%02d:%02d",
962     (int $m->[8] / 86400),
963     (int $m->[8] / 3600) % 24,
964     (int $m->[8] / 60) % 60,
965     $m->[8] % 60;
966    
967     $m = [$users, $host, $uptime, $version, $desc];
968    
969     $y++;
970    
971 root 1.62 $table->add (scalar @$m, $y, new CFClient::UI::VBox children => [
972     (new CFClient::UI::Button
973     text => "Use",
974     tooltip => "Put this server into the <b>Host:Port</b> field",
975     on_activate => sub {
976 root 1.75 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
977 root 1.62 $METASERVER->hide;
978 root 1.74 0
979 root 1.62 },
980     ),
981 root 1.1 (new CFClient::UI::Empty expand => 1),
982     ]);
983    
984 root 1.62 $table->add ($_, $y, new CFClient::UI::Label
985     ellipsise => 0,
986     align => $align[$_],
987     text => $m->[$_],
988     tooltip => $tip[$_],
989     can_hover => 1,
990     can_events => 1,
991     fontsize => 0.8)
992 root 1.1 for 0 .. $#$m;
993     }
994     }
995     });
996     }
997    
998 root 1.40 sub metaserver_dialog {
999 elmex 1.81 my $vbox = new CFClient::UI::VBox;
1000     my $table = new CFClient::UI::Table;
1001     $vbox->add (new CFClient::UI::ScrolledWindow expand => 1, child => $table);
1002    
1003 root 1.40 my $dialog = new CFClient::UI::FancyFrame
1004 root 1.62 title => "Server List",
1005     name => 'metaserver_dialog',
1006     x => 'center',
1007     y => 'center',
1008     z => 3,
1009     force_h => $::HEIGHT * 0.4,
1010 elmex 1.81 child => $vbox,
1011 root 1.80 has_close_button => 1,
1012 elmex 1.81 table => $table,
1013 root 1.40 on_visibility_change => sub {
1014 elmex 1.81 update_metaserver ($_[0]) if $_[1];
1015 root 1.74 0
1016 root 1.40 },
1017     ;
1018    
1019     $dialog
1020     }
1021    
1022 root 1.1 sub server_setup {
1023 root 1.49 my $vbox = new CFClient::UI::VBox;
1024 elmex 1.19
1025 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
1026     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
1027    
1028     {
1029     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
1030    
1031     $vbox->add (
1032 root 1.40 $HOST_ENTRY = new CFClient::UI::Entry
1033 root 1.1 expand => 1,
1034 root 1.75 text => $CFG->{profile}{default}{host},
1035 root 1.1 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1036 root 1.18 on_changed => sub {
1037 root 1.1 my ($self, $value) = @_;
1038 root 1.75 $CFG->{profile}{default}{host} = $value;
1039 root 1.74 0
1040 root 1.1 }
1041     );
1042    
1043 root 1.40 $vbox->add (new CFClient::UI::Button
1044     expand => 1,
1045     text => "Server List",
1046     other => $METASERVER,
1047 root 1.1 tooltip => "Show a list of available crossfire servers",
1048 root 1.74 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1049     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 },
1050 root 1.1 );
1051     }
1052    
1053     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
1054     $table->add (1, 4, new CFClient::UI::Entry
1055 root 1.75 text => $CFG->{profile}{default}{user},
1056 root 1.1 tooltip => "The name of your character on the server",
1057 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value }
1058 root 1.1 );
1059    
1060     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
1061     $table->add (1, 5, new CFClient::UI::Entry
1062 root 1.75 text => $CFG->{profile}{default}{password},
1063 root 1.1 hidden => 1,
1064     tooltip => "The password for your character",
1065 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value }
1066 root 1.1 );
1067    
1068     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
1069     $table->add (1, 7, new CFClient::UI::Slider
1070 root 1.30 force_w => 100,
1071 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1072     tooltip => "This is the size of the portion of the map update the server sends you. "
1073     . "If you set this to a high value you will be able to see further, "
1074     . "but you also increase bandwidth requirements and latency. "
1075     . "This option is only used once at log-in.",
1076 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 },
1077 root 1.1 );
1078    
1079     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1080     $table->add (1, 8, new CFClient::UI::CheckBox
1081     state => $CFG->{face_prefetch},
1082     tooltip => "<b>Background Image Prefetch</b>\n\n"
1083     . "If enabled, the client automatically pre-fetches images from the server. "
1084     . "This might increase or create lag, but increases the chances "
1085     . "of faces being ready for display when you encounter them. "
1086     . "It also uses up server bandwidth on every connect, "
1087     . "so only set it if you really need to prefetch images. "
1088     . "This option can be set and unset any time.",
1089 root 1.74 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
1090 root 1.1 );
1091    
1092     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
1093     $table->add (1, 9, new CFClient::UI::Entry
1094     text => $CFG->{output_count},
1095     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1096 root 1.74 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
1097 root 1.1 );
1098    
1099     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
1100     $table->add (1, 10, new CFClient::UI::Entry
1101     text => $CFG->{output_sync},
1102     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1103 root 1.74 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
1104 root 1.1 );
1105    
1106     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
1107     expand => 1,
1108     align => 0,
1109     text => "Login",
1110 root 1.18 on_activate => sub {
1111 root 1.1 $CONN ? stop_game
1112     : start_game;
1113 root 1.74 0
1114 root 1.1 },
1115     );
1116    
1117 root 1.49 $table->add (0, 12, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
1118     $table->add (1, 12, my $saycmd = new CFClient::UI::Entry
1119     text => $CFG->{say_command},
1120     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. "
1121     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
1122     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
1123     on_changed => sub {
1124     my ($self, $value) = @_;
1125     $CFG->{say_command} = $value;
1126 root 1.74 0
1127 root 1.49 }
1128     );
1129    
1130 root 1.67 $vbox->add (new CFClient::UI::Label
1131     text => "Server Info",
1132     fontsize => 1.2,
1133     padding_y => 8,
1134     fg => [1, 1, 0, 1],
1135     );
1136    
1137     $vbox->add ($SERVER_INFO = new CFClient::UI::Label ellipsise => 0);
1138    
1139 root 1.49 $vbox
1140 root 1.1 }
1141    
1142     sub message_window {
1143     my $window = new CFClient::UI::FancyFrame
1144 elmex 1.16 name => "message_window",
1145 root 1.1 title => "Messages",
1146     border_bg => [1, 1, 1, 1],
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 root 1.94 $FULLSCREEN_ENABLE->toggle;
1946 root 1.1 video_shutdown;
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