ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.119
Committed: Fri Sep 29 00:56:06 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
Changes since 1.118: +127 -69 lines
Log Message:
* implemented tip of the day window
* created some tips of the day, please add more
* rationalised errro report (and put client into debug mode :)
* add some random keyboard documentation

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