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