ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.116
Committed: Mon Aug 14 04:15:04 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
Changes since 1.115: +14 -3 lines
Log Message:
*** empty log message ***

File Contents

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