ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.117
Committed: Tue Aug 15 06:30:05 2006 UTC (17 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.116: +4 -2 lines
Log Message:
improved rename a little bit.

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