ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.121
Committed: Sun Oct 1 18:17:38 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
Changes since 1.120: +2 -7 lines
Log Message:
add test server login and editor invoke plus editor download/upload code

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