ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.93
Committed: Sat Jul 15 01:19:55 2006 UTC (17 years, 10 months ago) by root
Branch: MAIN
Changes since 1.92: +0 -1 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.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 root 1.30 x => "max",
1147     y => 0,
1148 root 1.60 force_w => $::WIDTH * 0.4,
1149     force_h => $::HEIGHT * 0.5,
1150 root 1.74 child => (my $vbox = new CFClient::UI::VBox),
1151     has_close_button => 1;
1152 root 1.1
1153     $vbox->add ($LOGVIEW);
1154    
1155     $vbox->add (my $input = new CFClient::UI::Entry
1156     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
1157     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
1158     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
1159     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
1160 root 1.18 on_focus_in => sub {
1161 root 1.1 my ($input, $prev_focus) = @_;
1162    
1163     delete $input->{refocus_map};
1164    
1165     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
1166     $input->{refocus_map} = 1;
1167     }
1168     delete $input->{auto_activated};
1169 root 1.74
1170     0
1171 root 1.1 },
1172 root 1.18 on_activate => sub {
1173 root 1.1 my ($input, $text) = @_;
1174     $input->set_text ('');
1175    
1176 elmex 1.46 if ($text =~ /^\/(.*)/) {
1177 root 1.1 $::CONN->user_send ($1);
1178     } else {
1179     my $say_cmd = $::CFG->{say_command} || 'say';
1180     $::CONN->user_send ("$say_cmd $text");
1181     }
1182     if ($input->{refocus_map}) {
1183     delete $input->{refocus_map};
1184     $MAPWIDGET->focus_in
1185     }
1186 root 1.74
1187     0
1188 root 1.1 },
1189 root 1.18 on_escape => sub {
1190 root 1.74 $MAPWIDGET->grab_focus;
1191    
1192     0
1193 root 1.1 },
1194     );
1195    
1196     $CONSOLE = {
1197     window => $window,
1198 root 1.30 input => $input,
1199 root 1.1 };
1200    
1201     $window
1202     }
1203    
1204     sub open_quit_dialog {
1205     unless ($QUIT_DIALOG) {
1206 root 1.30 $QUIT_DIALOG = new CFClient::UI::FancyFrame
1207     x => "center",
1208     y => "center",
1209 root 1.55 z => 50,
1210 root 1.30 title => "Really Quit?",
1211     ;
1212 root 1.1
1213     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
1214    
1215     $vb->add (new CFClient::UI::Label
1216     text => "You should find a savebed and apply it first!",
1217     max_w => $WIDTH * 0.25,
1218     ellipsize => 0,
1219     );
1220     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
1221     $hb->add (new CFClient::UI::Button
1222     text => "Ok",
1223     expand => 1,
1224 root 1.74 on_activate => sub { $QUIT_DIALOG->hide; 0 },
1225 root 1.1 );
1226     $hb->add (new CFClient::UI::Button
1227     text => "Quit anyway",
1228     expand => 1,
1229 root 1.18 on_activate => sub { exit },
1230 root 1.1 );
1231 root 1.21 }
1232 root 1.1
1233 root 1.21 $QUIT_DIALOG->show;
1234 root 1.1 }
1235    
1236 root 1.49 sub autopickup_setup {
1237 root 1.51 my $table = new CFClient::UI::Table;
1238 elmex 1.44
1239 elmex 1.43 for (
1240 root 1.51 ["General", 0, 0,
1241 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1242 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1243     ["Stop before pickup" => PICKUP_STOP],
1244     ["Debug autopickup" => PICKUP_DEBUG],
1245 root 1.51 ],
1246     ["Weapons", 0, 6,
1247 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1248     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1249     ["Bows" => PICKUP_BOW],
1250     ["Arrows" => PICKUP_ARROW],
1251 root 1.51 ],
1252     ["Armour", 0, 12,
1253 root 1.58 ["Helmets" => PICKUP_HELMET],
1254     ["Shields" => PICKUP_SHIELD],
1255     ["Body Armour" => PICKUP_ARMOUR],
1256     ["Boots" => PICKUP_BOOTS],
1257     ["Gloves" => PICKUP_GLOVES],
1258     ["Cloaks" => PICKUP_CLOAK],
1259 root 1.51 ],
1260    
1261     ["Readables", 2, 2,
1262 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1263     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1264     ["Normal Books/Scrolls" => PICKUP_READABLES],
1265 root 1.51 ],
1266     ["Misc", 2, 7,
1267 root 1.58 ["Food" => PICKUP_FOOD],
1268     ["Drinks" => PICKUP_DRINK],
1269     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1270     ["Keys" => PICKUP_KEY],
1271     ["Magical Items" => PICKUP_MAGICAL],
1272     ["Potions" => PICKUP_POTION],
1273     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1274     ["Ignore cursed" => PICKUP_NOT_CURSED],
1275     ["Jewelery" => PICKUP_JEWELS],
1276 root 1.51 ],
1277 elmex 1.66 ["Weight/Value ratio", 2, 17]
1278 elmex 1.43 )
1279     {
1280 root 1.51 my ($title, $x, $y, @bits) = @$_;
1281     $table->add ($x, $y, new CFClient::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1282    
1283     for (@bits) {
1284     ++$y;
1285    
1286 elmex 1.43 my $mask = $_->[1];
1287 root 1.51 $table->add ($x , $y, new CFClient::UI::Label text => $_->[0], align => 1, expand => 1);
1288 root 1.86 $table->add ($x+1, $y, my $checkbox = new CFClient::UI::CheckBox
1289 elmex 1.83 state => $::CFG->{pickup} & $mask,
1290 elmex 1.43 on_changed => sub {
1291     my ($box, $value) = @_;
1292 root 1.63
1293 elmex 1.43 if ($value) {
1294 elmex 1.45 $::CFG->{pickup} |= $mask;
1295 elmex 1.43 } else {
1296 root 1.63 $::CFG->{pickup} &= ~$mask;
1297 elmex 1.43 }
1298 root 1.63
1299     $::CONN->send_command ("pickup $::CFG->{pickup}")
1300 elmex 1.45 if defined $::CONN;
1301 root 1.74
1302     0
1303 elmex 1.43 });
1304 root 1.86
1305     ${$_->[2]} = $checkbox if $_->[2];
1306 elmex 1.43 }
1307     }
1308    
1309 elmex 1.66 $table->add (2, 18, new CFClient::UI::ValSlider
1310 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1311     template => ">= 99",
1312 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1313     on_changed => sub {
1314     my ($slider, $value) = @_;
1315    
1316 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1317 elmex 1.66 $::CFG->{pickup} |= int $value
1318     if $value;
1319     1;
1320     });
1321 elmex 1.83
1322 elmex 1.66 $table->add (3, 18, new CFClient::UI::Button
1323     text => "set",
1324     on_activate => sub {
1325     $::CONN->send_command ("pickup $::CFG->{pickup}")
1326     if defined $::CONN;
1327 root 1.74 0
1328 elmex 1.66 });
1329    
1330 root 1.51 $table
1331 elmex 1.43 }
1332    
1333 elmex 1.85 sub inventory_widget {
1334     my $hb = new CFClient::UI::HBox homogeneous => 1;
1335 root 1.1
1336 root 1.21 $hb->add (my $vb1 = new CFClient::UI::VBox);
1337     $vb1->add (new CFClient::UI::Label align => 0, text => "Player");
1338 elmex 1.85 $vb1->add ($INV = new CFClient::UI::Inventory);
1339 root 1.1
1340 root 1.21 $hb->add (my $vb2 = new CFClient::UI::VBox);
1341 elmex 1.17
1342 elmex 1.27 $vb2->add ($INV_RIGHT_HB = new CFClient::UI::HBox);
1343 elmex 1.14
1344 elmex 1.85 $vb2->add ($INVR = new CFClient::UI::Inventory);
1345 root 1.1
1346 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1347     CFClient::Protocol::set_opencont ($::CONN, 0, "Floor");
1348    
1349 elmex 1.85 $hb
1350 root 1.1 }
1351    
1352 root 1.86 sub toggle_player_page {
1353     my ($widget) = @_;
1354    
1355     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1356     $PL_WINDOW->hide;
1357     } else {
1358     $PL_NOTEBOOK->set_current_page ($widget);
1359     $PL_WINDOW->show;
1360     }
1361     }
1362    
1363 elmex 1.85 sub player_window {
1364     my $plwin = $PL_WINDOW = new CFClient::UI::FancyFrame
1365     x => "center",
1366     y => "center",
1367     force_w => $WIDTH * 9/10,
1368     force_h => $HEIGHT * 9/10,
1369     title => "Player",
1370 elmex 1.90 name => "playerbook",
1371 elmex 1.85 has_close_button => 1
1372     ;
1373    
1374     my $ntb =
1375     $PL_NOTEBOOK =
1376     new CFClient::UI::Notebook
1377     expand => 1,
1378     debug => 1,
1379     filter => (new CFClient::UI::ScrolledWindow expand => 1, scroll_y => 1),
1380     ;
1381 root 1.86
1382 elmex 1.85 $ntb->add (
1383 elmex 1.92 "Stats" => $STATS_PAGE = stats_window,
1384     "Shows statistics, where all your Stats and Resistances are shown."
1385     );
1386     $ntb->add (
1387     "Skills" => $STATS_PAGE = skill_window,
1388     "Shows all your Skills."
1389 elmex 1.85 );
1390     $ntb->add (
1391 root 1.87 Spellbook => $SPELL_PAGE = new CFClient::UI::SpellList,
1392 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1393 elmex 1.85 );
1394     $ntb->add (
1395 root 1.86 Inventory => $INVENTORY_PAGE = inventory_widget,
1396     "Toggles the inventory window, where you can manage your loot (or treasures :). "
1397     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1398 elmex 1.85 );
1399    
1400 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1401 root 1.86
1402 elmex 1.85 $plwin->add ($ntb);
1403     $plwin
1404 elmex 1.38 }
1405    
1406 elmex 1.77 sub update_bindings {
1407     $BIND_UPD_CB->() if $BIND_UPD_CB;
1408     }
1409    
1410 root 1.49 sub keyboard_setup {
1411 elmex 1.24 my $binding_list = new CFClient::UI::VBox;
1412    
1413 elmex 1.34 my $refresh;
1414 elmex 1.77 $refresh = $BIND_UPD_CB = sub {
1415 elmex 1.24 $binding_list->clear ();
1416    
1417 root 1.75 for my $mod (keys %{$::CFG->{profile}{default}{bindings}}) {
1418     for my $sym (keys %{$::CFG->{profile}{default}{bindings}{$mod}}) {
1419     my $cmds = $::CFG->{profile}{default}{bindings}{$mod}{$sym};
1420 elmex 1.24 next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1421    
1422     my $lbl = join "; ", @$cmds;
1423 root 1.84 my $nam = CFClient::BindingEditor::keycombo_to_name ($mod, $sym);
1424 elmex 1.24 $binding_list->add (my $hb = new CFClient::UI::HBox);
1425     $hb->add (new CFClient::UI::Button
1426 elmex 1.25 text => "delete",
1427 elmex 1.34 tooltip => "Deletes the binding",
1428 elmex 1.24 on_activate => sub {
1429     $binding_list->remove ($hb);
1430 root 1.75 delete $::CFG->{profile}{default}{bindings}{$mod}{$sym};
1431 root 1.74 0
1432 elmex 1.24 });
1433 elmex 1.34
1434     $hb->add (new CFClient::UI::Button
1435     text => "edit",
1436     tooltip => "Edits the binding",
1437     on_activate => sub {
1438     $::BIND_EDITOR->set_binding (
1439 root 1.75 $mod, $sym, $::CFG->{profile}{default}{bindings}{$mod}{$sym},
1440 elmex 1.34 sub {
1441     my ($nmod, $nsym, $ncmds) = @_;
1442 elmex 1.77 $::BIND_EDITOR->cfg_unbind ($mod, $sym);
1443     $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds);
1444 elmex 1.34 $refresh->();
1445 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1446     $SETUP_DIALOG->show;
1447 elmex 1.34 },
1448     sub {
1449 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1450     $SETUP_DIALOG->show;
1451 elmex 1.34 });
1452     $::BIND_EDITOR->show;
1453 root 1.49 $SETUP_DIALOG->hide;
1454 root 1.74 0
1455 elmex 1.34 });
1456    
1457     $hb->add (new CFClient::UI::Label text => "(Key: $nam)");
1458 elmex 1.24 $hb->add (new CFClient::UI::Label text => $lbl, expand => 1);
1459     }
1460     }
1461     };
1462    
1463 root 1.49 my $vb = new CFClient::UI::VBox;
1464 elmex 1.71 $vb->add (my $hb = new CFClient::UI::HBox);
1465     $hb->add (new CFClient::UI::Label text => "only shift-up stops fire");
1466     $hb->add (new CFClient::UI::CheckBox
1467     expand => 1,
1468     state => $CFG->{shift_fire_stop},
1469     tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift",
1470     on_changed => sub {
1471     my ($cbox, $value) = @_;
1472     $CFG->{shift_fire_stop} = $value;
1473 root 1.74 0
1474 elmex 1.71 });
1475    
1476 elmex 1.35 $vb->add ($binding_list);
1477     $vb->add (my $hb = new CFClient::UI::HBox);
1478 root 1.49
1479 elmex 1.35 $hb->add (new CFClient::UI::Button
1480 elmex 1.34 text => "record new",
1481 elmex 1.35 expand => 1,
1482 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1483     on_activate => sub {
1484     $::BIND_EDITOR->set_binding (undef, undef, [],
1485     sub {
1486     my ($mod, $sym, $cmds) = @_;
1487 elmex 1.77 $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds);
1488 elmex 1.34 $refresh->();
1489 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1490     $SETUP_DIALOG->show;
1491 elmex 1.34 },
1492     sub {
1493 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1494     $SETUP_DIALOG->show;
1495 root 1.53 },
1496     );
1497 root 1.49 $SETUP_DIALOG->hide;
1498 elmex 1.34 $::BIND_EDITOR->show;
1499 root 1.74 0
1500 elmex 1.34 },
1501     );
1502 root 1.49
1503 elmex 1.35 $hb->add (new CFClient::UI::Button
1504     text => "close",
1505     tooltip => "Closes the binding window",
1506     expand => 1,
1507     on_activate => sub {
1508 root 1.49 $SETUP_DIALOG->hide;
1509 root 1.74 0
1510 elmex 1.35 }
1511     );
1512    
1513 elmex 1.24 $refresh->();
1514 root 1.49
1515     $vb
1516 elmex 1.24 }
1517    
1518 root 1.64 sub help_window {
1519 root 1.1 my $win = new CFClient::UI::FancyFrame
1520 root 1.41 x => 'center',
1521     y => 'center',
1522 root 1.55 z => 2,
1523 root 1.41 name => 'doc_browser',
1524     force_w => int $WIDTH * 7/8,
1525     force_h => int $HEIGHT * 7/8,
1526 root 1.87 title => "Help Browser",
1527     has_close_button => 1;
1528 root 1.1
1529     $win->add (my $vbox = new CFClient::UI::VBox);
1530    
1531     $vbox->add (my $buttons = new CFClient::UI::HBox);
1532 root 1.64 $vbox->add (my $viewer = new CFClient::UI::TextScroller
1533     expand => 1, fontsize => 0.8, padding_x => 4);
1534 root 1.1
1535 root 1.64 $buttons->add (new CFClient::UI::Label text => "Choose a document to display: ");
1536     $buttons->add (my $combo = new CFClient::UI::Combobox
1537     value => undef,
1538     options => [
1539     [intro => "Introduction"],
1540 root 1.78 [manual => "Main Manual"],
1541     [skill_help => "Skill Reference"],
1542     [command_help => "Command Reference"],
1543 root 1.64 [dmcommand_help => "DM Commands"],
1544     [COPYING => "License Terms"],
1545     ],
1546     on_changed => sub {
1547     my ($self, $pod) = @_;
1548 root 1.1
1549 root 1.64 my $pom = CFClient::load_pod CFClient::find_rcfile "pod/$pod.pod",
1550     doc_viewer => 1, sub { CFClient::pod_to_pango_list $_[0] };
1551 root 1.1
1552 root 1.64 $viewer->clear;
1553 root 1.78
1554 root 1.79 # $viewer->add_paragraph ([1, 1, 1, 1], ["<big>Test</big>\n\n \x{fffc} \x{fffc}\n",
1555     # (new CFClient::UI::Image path => "x.png", can_hover => 1, can_events => 1),
1556     # (new CFClient::UI::Label text => "üüüü", can_hover => 1, can_events => 1, tooltip => "??"),
1557 root 1.78 # ]);#d#
1558 root 1.64
1559     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
1560     for @$pom;
1561 root 1.1
1562 root 1.64 $viewer->set_offset (0);
1563 root 1.78
1564 root 1.74 0
1565 root 1.64 },
1566     on_visibility_change => sub {
1567     my ($self, $visible) = @_;
1568     return unless $visible;
1569     return if $self->{value};
1570     $self->set_value ("intro");
1571 root 1.74 0
1572 root 1.64 },
1573     );
1574 root 1.1
1575     $win
1576     }
1577    
1578     sub sdl_init {
1579     CFClient::SDL_Init
1580     and die "SDL::Init failed!\n";
1581     }
1582    
1583     sub video_init {
1584     sdl_init;
1585    
1586     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1587    
1588     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1589    
1590     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1591     $FULLSCREEN = $CFG->{fullscreen};
1592     $FAST = $CFG->{fast};
1593    
1594     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1595     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1596    
1597     $SDL_ACTIVE = 1;
1598     $LAST_REFRESH = time - 0.01;
1599    
1600 root 1.10 CFClient::OpenGL::init;
1601 root 1.1
1602     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1603    
1604     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1605    
1606     #############################################################################
1607    
1608     if ($DEBUG_STATUS) {
1609     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1610     } else {
1611     # create the widgets
1612    
1613 root 1.30 $DEBUG_STATUS = new CFClient::UI::Label
1614     padding => 0,
1615     z => 100,
1616     force_x => "max",
1617     force_y => 0;
1618 root 1.1 $DEBUG_STATUS->show;
1619 elmex 1.34
1620 root 1.80 $BIND_EDITOR = new CFClient::BindingEditor (x => "max", y => 0);
1621 elmex 1.34
1622 root 1.1 $STATUSBOX = new CFClient::UI::Statusbox;
1623 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1624 root 1.1
1625     (new CFClient::UI::Frame
1626     bg => [0, 0, 0, 0.4],
1627 root 1.30 force_x => 0,
1628     force_y => "max",
1629 root 1.1 child => $STATUSBOX,
1630     )->show;
1631    
1632     CFClient::UI::FancyFrame->new (
1633 root 1.47 title => "Map",
1634 root 1.42 name => "mapmap",
1635 root 1.30 x => 0,
1636     y => $FONTSIZE + 8,
1637 root 1.1 border_bg => [1, 1, 1, 192/255],
1638     bg => [1, 1, 1, 0],
1639     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1640     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1641     ),
1642     )->show;
1643    
1644     $MAPWIDGET = new CFClient::MapWidget;
1645     $MAPWIDGET->connect (activate_console => sub {
1646     my ($mapwidget, $preset) = @_;
1647    
1648     if ($CONSOLE) {
1649     $CONSOLE->{input}->{auto_activated} = 1;
1650 root 1.74 $CONSOLE->{input}->grab_focus;
1651 root 1.1
1652     if ($preset && $CONSOLE->{input}->get_text eq '') {
1653     $CONSOLE->{input}->set_text ($preset);
1654     }
1655     }
1656     });
1657     $MAPWIDGET->show;
1658 root 1.74 $MAPWIDGET->grab_focus;
1659 root 1.1
1660 root 1.64 $LOGVIEW = new CFClient::UI::TextScroller
1661 root 1.1 expand => 1,
1662     font => $FONT_FIXED,
1663     fontsize => $::CFG->{log_fontsize},
1664 root 1.61 indent => -4,
1665 root 1.1 can_hover => 1,
1666     can_events => 1,
1667     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1668     ;
1669    
1670 root 1.49 $SETUP_DIALOG = new CFClient::UI::FancyFrame
1671     title => "Setup",
1672     name => "setup_dialog",
1673     x => 'center',
1674     y => 'center',
1675 root 1.53 z => 2,
1676 root 1.49 force_w => $::WIDTH * 0.6,
1677     force_h => $::HEIGHT * 0.6,
1678 root 1.74 has_close_button => 1,
1679 root 1.49 ;
1680    
1681 elmex 1.81 $METASERVER = metaserver_dialog;
1682    
1683 root 1.50 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFClient::UI::Notebook expand => 1, debug => 1,
1684 root 1.60 filter => new CFClient::UI::ScrolledWindow expand => 1, scroll_y => 1);
1685 root 1.49
1686     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1687     "Configure the server to play on, your username, password and other server-related options.");
1688     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1689 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1690 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1691     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1692     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1693     "Configure the use of audio, sound effects and background music.");
1694     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1695 root 1.75 "Lets you define, edit and delete key bindings."
1696     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1697 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1698 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1699 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1700     . "binding editor closes");
1701 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1702 root 1.75 "Some debuggin' options. Do not ask.");
1703 root 1.49
1704 root 1.57 $BUTTONBAR = new CFClient::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1705 root 1.1
1706 root 1.49 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1707     tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1708    
1709 root 1.60 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1710 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1711    
1712     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
1713    
1714 root 1.87 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Playerbook", other => player_window,
1715 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1716 root 1.1
1717     $BUTTONBAR->add (new CFClient::UI::Button
1718     text => "Save Config",
1719     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1720 root 1.18 on_activate => sub {
1721 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1722 root 1.28 CFClient::write_cfg "$Crossfire::VARDIR/cfplusrc";
1723 root 1.1 status "Configuration Saved";
1724 root 1.74 0
1725 root 1.1 },
1726     );
1727    
1728 root 1.86 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1729 root 1.1 tooltip => "View Documentation");
1730    
1731     $BUTTONBAR->add (new CFClient::UI::Button
1732 root 1.18 text => "Quit",
1733     tooltip => "Terminates the program",
1734     on_activate => sub {
1735 root 1.1 if ($CONN) {
1736     open_quit_dialog;
1737     } else {
1738     exit;
1739     }
1740 root 1.74 0
1741 root 1.1 },
1742     );
1743    
1744     $BUTTONBAR->show;
1745 root 1.49 $SETUP_DIALOG->show;
1746     }
1747 root 1.1
1748 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1749 root 1.1 }
1750    
1751     sub video_shutdown {
1752 root 1.73 CFClient::OpenGL::shutdown;
1753    
1754 root 1.1 undef $SDL_ACTIVE;
1755     }
1756    
1757     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1758     my $bgmusic;#TODO#hack#d#
1759    
1760     sub audio_channel_finished {
1761     my ($channel) = @_;
1762    
1763     #warn "channel $channel finished\n";#d#
1764     }
1765    
1766     sub audio_music_finished {
1767     return unless $CFG->{bgm_enable};
1768    
1769     # TODO: hack, do play loop and mood music
1770     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1771     $bgmusic->play (0);
1772    
1773     push @bgmusic, shift @bgmusic;
1774     }
1775    
1776     sub audio_init {
1777     if ($CFG->{audio_enable}) {
1778     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1779     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1780    
1781     unless ($SDL_MIXER) {
1782     status "Unable to open sound device: there will be no sound";
1783     return;
1784     }
1785    
1786     CFClient::Mix_AllocateChannels 8;
1787     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1788    
1789     audio_music_finished;
1790    
1791     while (<$fh>) {
1792     next if /^\s*#/;
1793     next if /^\s*$/;
1794    
1795     my ($file, $volume, $event) = split /\s+/, $_, 3;
1796    
1797     push @SOUNDS, "$volume,$file";
1798    
1799     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1800     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1801     $chunk->volume ($volume * 128 / 100);
1802     $chunk
1803     };
1804     }
1805     } else {
1806     status "unable to open sound config: $!";
1807     }
1808     }
1809     }
1810    
1811     sub audio_shutdown {
1812     CFClient::Mix_CloseAudio if $SDL_MIXER;
1813     undef $SDL_MIXER;
1814     @SOUNDS = ();
1815     %AUDIO_CHUNKS = ();
1816     }
1817    
1818     my %animate_object;
1819     my $animate_timer;
1820    
1821     my $fps = 9;
1822    
1823     my %demo;#d#
1824    
1825     sub force_refresh {
1826     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1827 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1828 root 1.1
1829     $CFClient::UI::ROOT->draw;
1830    
1831     $WANT_REFRESH = 0;
1832     $CAN_REFRESH = 0;
1833     $LAST_REFRESH = $NOW;
1834    
1835     0 && do {
1836     # some weird model-drawing code, just a joke right now
1837     use CFClient::OpenGL;
1838    
1839     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1840     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1841     $demo{r} ||= do {
1842     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1843     $mod->{v} = pack "f*", @{$mod->{v}};
1844     $_ = [scalar @$_, pack "S!*", @$_]
1845     for values %{$mod->{g}};
1846     $mod
1847     };
1848    
1849     my $r = $demo{r} or die;
1850    
1851     glDepthMask 1;
1852     glClear GL_DEPTH_BUFFER_BIT;
1853     glEnable GL_TEXTURE_2D;
1854     glEnable GL_DEPTH_TEST;
1855     glEnable GL_CULL_FACE;
1856     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1857    
1858     glMatrixMode GL_PROJECTION;
1859     glLoadIdentity;
1860     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1861     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1862     glMatrixMode GL_MODELVIEW;
1863     glLoadIdentity;
1864    
1865     glPushMatrix;
1866     glTranslate 0, 0, -800;
1867     glScale 1, -1, 1;
1868     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1869     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1870     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1871     glScale 50, 50, 50;
1872    
1873     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1874     while (my ($k, $v) = each %{$r->{g}}) {
1875     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1876     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1877     }
1878    
1879     glPopMatrix;
1880    
1881     glShadeModel GL_FLAT;
1882     glDisable GL_DEPTH_TEST;
1883     glDisable GL_TEXTURE_2D;
1884     glDepthMask 0;
1885    
1886     $WANT_REFRESH++;
1887     };
1888    
1889     CFClient::SDL_GL_SwapBuffers;
1890     }
1891    
1892 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1893 root 1.1 $NOW = time;
1894    
1895     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1896     for CFClient::SDL_PollEvent;
1897    
1898     if (%animate_object) {
1899     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1900     $WANT_REFRESH++;
1901     }
1902    
1903     if ($WANT_REFRESH) {
1904     force_refresh;
1905     } else {
1906     $CAN_REFRESH = 1;
1907     }
1908     });
1909    
1910     sub animation_start {
1911     my ($widget) = @_;
1912     $animate_object{$widget} = $widget;
1913     }
1914    
1915     sub animation_stop {
1916     my ($widget) = @_;
1917     delete $animate_object{$widget};
1918     }
1919    
1920     # check once/second for faces that need to be prefetched
1921     # this should, of course, only run on demand, but
1922     # SDL forces worse things on us....
1923    
1924     Event->timer (after => 1, interval => 0.25, cb => sub {
1925     $CONN->face_prefetch
1926     if $CONN;
1927     });
1928    
1929     %SDL_CB = (
1930     CFClient::SDL_QUIT => sub {
1931     Event::unloop -1;
1932     },
1933     CFClient::SDL_VIDEORESIZE => sub {
1934     },
1935     CFClient::SDL_VIDEOEXPOSE => sub {
1936     CFClient::UI::full_refresh;
1937     },
1938     CFClient::SDL_ACTIVEEVENT => sub {
1939     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1940     },
1941     CFClient::SDL_KEYDOWN => sub {
1942     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1943     # alt-enter
1944     video_shutdown;
1945     $CFG->{fullscreen} = !$CFG->{fullscreen};
1946     video_init;
1947     } else {
1948     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1949     }
1950     },
1951     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1952     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1953     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1954     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1955     CFClient::SDL_USEREVENT => sub {
1956     if ($_[0]{code} == 1) {
1957     audio_channel_finished $_[0]{data1};
1958     } elsif ($_[0]{code} == 0) {
1959     audio_music_finished;
1960     }
1961     },
1962     );
1963    
1964     #############################################################################
1965    
1966     $SIG{INT} = $SIG{TERM} = sub { exit };
1967    
1968     {
1969 root 1.49 local $SIG{__DIE__} = sub {
1970     return unless defined $^S && !$^S;
1971     Carp::confess $_[1];#d#TODO: remove when stable
1972     CFClient::fatal $_[0];
1973     };
1974 root 1.1
1975 root 1.28 CFClient::read_cfg "$Crossfire::VARDIR/cfplusrc";
1976 elmex 1.16 CFClient::UI::set_layout ($::CFG->{layout});
1977 root 1.1
1978     my %DEF_CFG = (
1979 root 1.75 sdl_mode => 0,
1980     width => 640,
1981     height => 480,
1982     fullscreen => 0,
1983     fast => 0,
1984     map_scale => 1,
1985     fow_enable => 1,
1986     fow_intensity => 0.45,
1987     fow_smooth => 0,
1988     gui_fontsize => 1,
1989     log_fontsize => 0.7,
1990     gauge_fontsize => 1,
1991     gauge_size => 0.35,
1992     stat_fontsize => 0.7,
1993     mapsize => 100,
1994     say_command => 'say',
1995     audio_enable => 1,
1996     bgm_enable => 1,
1997     bgm_volume => 0.25,
1998     face_prefetch => 0,
1999     output_sync => 1,
2000     output_count => 1,
2001     pickup => 0,
2002     default => "profile", # default profile
2003 root 1.1 );
2004 root 1.75
2005 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2006     $CFG->{$k} = $v unless exists $CFG->{$k};
2007     }
2008    
2009 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2010    
2011 root 1.1 sdl_init;
2012    
2013     @SDL_MODES = reverse
2014     grep $_->[0] >= 640 && $_->[1] >= 480,
2015     CFClient::SDL_ListModes;
2016    
2017     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2018    
2019     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2020    
2021     {
2022     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
2023     DejaVuSans.ttf
2024     DejaVuSansMono.ttf
2025     DejaVuSans-Bold.ttf
2026     DejaVuSansMono-Bold.ttf
2027     DejaVuSans-Oblique.ttf
2028     DejaVuSansMono-Oblique.ttf
2029     DejaVuSans-BoldOblique.ttf
2030     DejaVuSansMono-BoldOblique.ttf
2031     );
2032    
2033     CFClient::add_font $_ for @fonts;
2034    
2035     CFClient::pango_init;
2036    
2037     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
2038     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
2039    
2040     $FONT_PROP->make_default;
2041     }
2042    
2043     # compare mono (ft) vs. rgba (cairo)
2044     # ft - 1.8s, cairo 3s, even in alpha-only mode
2045     # for my $rgba (0..1) {
2046     # my $t1 = Time::HiRes::time;
2047     # for (1..1000) {
2048     # my $layout = CFClient::Layout->new ($rgba);
2049     # $layout->set_text ("hallo" x 100);
2050     # $layout->render;
2051     # }
2052     # my $t2 = Time::HiRes::time;
2053     # warn $t2-$t1;
2054     # }
2055    
2056     video_init;
2057     audio_init;
2058     }
2059    
2060     Event::loop;
2061 root 1.69 #CFClient::SDL_Quit;
2062     #CFClient::_exit 0;
2063 root 1.1
2064     END { CFClient::SDL_Quit }
2065    
2066     =head1 NAME
2067    
2068 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2069 root 1.1
2070     =head1 SYNOPSIS
2071    
2072     Just run it - no commandline arguments are supported.
2073    
2074     =head1 USAGE
2075    
2076 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2077 root 1.1 fullscreen and interactively.
2078    
2079 root 1.39 =head1 DEBUGGING
2080    
2081    
2082     CFPLUS_DEBUG - environment variable
2083    
2084     1 draw borders around widgets
2085     2 add low-level widget info to tooltips
2086     4 show fps
2087     8 suppress tooltips
2088    
2089 root 1.1 =head1 AUTHOR
2090    
2091     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2092    
2093    
2094