ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.127
Committed: Sat Nov 18 22:36:00 2006 UTC (17 years, 6 months ago) by root
Branch: MAIN
Changes since 1.126: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

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