ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.120
Committed: Sun Oct 1 14:48:51 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
Changes since 1.119: +16 -8 lines
Log Message:
support for grabbing win32 proxy

File Contents

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