ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.122
Committed: Sun Oct 8 21:22:30 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
Changes since 1.121: +35 -1 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     if ($^O eq "MSWin32") {
8     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     our $META_SERVER = "crossfire.real-time.com:13326";
81    
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.104 $MAP = new CFPlus::Map $mapsize, $mapsize;
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     my $buf;
863    
864     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
865    
866     unless ($fh) {
867     $label->set_text ("unable to contact metaserver: $!");
868     return;
869     }
870    
871     Event->io (fd => $fh, poll => 'r', cb => sub {
872     my $res = sysread $fh, $buf, 8192, length $buf;
873    
874     if (!defined $res) {
875     $_[0]->w->cancel;
876     $label->set_text ("error while retrieving server list: $!");
877     } elsif ($res == 0) {
878     $_[0]->w->cancel;
879     status "server list retrieved";
880    
881     utf8::decode $buf if utf8::valid $buf;
882    
883     $table->clear;
884    
885 root 1.62 my @tip = (
886     "The current number of users logged in on the server.",
887     "The hostname of the server.",
888     "The time this server has been running without being restarted.",
889     "The server software version - a '+' indicates a Crossfire+ server.",
890     "Short information about this server provided by its admins.",
891     );
892     my @col = qw(#Users Host Uptime Version Description);
893 root 1.104 $table->add ($_, 0, new CFPlus::UI::Label
894 root 1.62 can_hover => 1, can_events => 1,
895     align => 0, fg => [1, 1, 0],
896     text => $col[$_], tooltip => $tip[$_])
897     for 0 .. $#col;
898 root 1.1
899     my @align = qw(1 0 1 1 -1);
900    
901     my $y = 0;
902     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
903     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
904    
905     for ($desc) {
906     s/<br>/\n/gi;
907     s/<li>/\n· /gi;
908     s/<.*?>//sgi;
909     s/&/&amp;/g;
910     s/</&lt;/g;
911     s/>/&gt;/g;
912     }
913    
914     $uptime = sprintf "%dd %02d:%02d:%02d",
915     (int $m->[8] / 86400),
916     (int $m->[8] / 3600) % 24,
917     (int $m->[8] / 60) % 60,
918     $m->[8] % 60;
919    
920     $m = [$users, $host, $uptime, $version, $desc];
921    
922     $y++;
923    
924 root 1.104 $table->add (scalar @$m, $y, new CFPlus::UI::VBox children => [
925     (new CFPlus::UI::Button
926 root 1.62 text => "Use",
927     tooltip => "Put this server into the <b>Host:Port</b> field",
928     on_activate => sub {
929 root 1.75 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
930 root 1.62 $METASERVER->hide;
931 root 1.74 0
932 root 1.62 },
933     ),
934 root 1.104 (new CFPlus::UI::Empty expand => 1),
935 root 1.1 ]);
936    
937 root 1.104 $table->add ($_, $y, new CFPlus::UI::Label
938 root 1.62 ellipsise => 0,
939     align => $align[$_],
940     text => $m->[$_],
941     tooltip => $tip[$_],
942     can_hover => 1,
943     can_events => 1,
944     fontsize => 0.8)
945 root 1.1 for 0 .. $#$m;
946     }
947     }
948     });
949     }
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     force_h => $::HEIGHT * 0.4,
963 elmex 1.81 child => $vbox,
964 root 1.80 has_close_button => 1,
965 elmex 1.81 table => $table,
966 root 1.40 on_visibility_change => sub {
967 elmex 1.81 update_metaserver ($_[0]) if $_[1];
968 root 1.74 0
969 root 1.40 },
970     ;
971    
972     $dialog
973     }
974    
975 root 1.1 sub server_setup {
976 root 1.104 my $vbox = new CFPlus::UI::VBox;
977 elmex 1.19
978 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
979     label => "Connection Settings",
980     child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]),
981     );
982 root 1.104 $table->add (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
983 root 1.1
984     {
985 root 1.104 $table->add (1, 2, my $vbox = new CFPlus::UI::VBox);
986 root 1.1
987     $vbox->add (
988 root 1.104 $HOST_ENTRY = new CFPlus::UI::Entry
989 root 1.1 expand => 1,
990 root 1.75 text => $CFG->{profile}{default}{host},
991 root 1.1 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
992 root 1.18 on_changed => sub {
993 root 1.1 my ($self, $value) = @_;
994 root 1.75 $CFG->{profile}{default}{host} = $value;
995 root 1.74 0
996 root 1.1 }
997     );
998    
999 root 1.104 $vbox->add (new CFPlus::UI::Button
1000 root 1.40 expand => 1,
1001     text => "Server List",
1002     other => $METASERVER,
1003 root 1.1 tooltip => "Show a list of available crossfire servers",
1004 root 1.74 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1005     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 },
1006 root 1.1 );
1007     }
1008    
1009 root 1.104 $table->add (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
1010     $table->add (1, 4, new CFPlus::UI::Entry
1011 root 1.75 text => $CFG->{profile}{default}{user},
1012 root 1.1 tooltip => "The name of your character on the server",
1013 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value }
1014 root 1.1 );
1015    
1016 root 1.104 $table->add (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
1017     $table->add (1, 5, new CFPlus::UI::Entry
1018 root 1.75 text => $CFG->{profile}{default}{password},
1019 root 1.1 hidden => 1,
1020     tooltip => "The password for your character",
1021 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value }
1022 root 1.1 );
1023    
1024 root 1.104 $table->add (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1025     $table->add (1, 7, new CFPlus::UI::Slider
1026 root 1.30 force_w => 100,
1027 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1028     tooltip => "This is the size of the portion of the map update the server sends you. "
1029     . "If you set this to a high value you will be able to see further, "
1030     . "but you also increase bandwidth requirements and latency. "
1031     . "This option is only used once at log-in.",
1032 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 },
1033 root 1.1 );
1034    
1035 root 1.104 $table->add (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1036     $table->add (1, 8, new CFPlus::UI::CheckBox
1037 root 1.1 state => $CFG->{face_prefetch},
1038     tooltip => "<b>Background Image Prefetch</b>\n\n"
1039     . "If enabled, the client automatically pre-fetches images from the server. "
1040     . "This might increase or create lag, but increases the chances "
1041     . "of faces being ready for display when you encounter them. "
1042     . "It also uses up server bandwidth on every connect, "
1043     . "so only set it if you really need to prefetch images. "
1044     . "This option can be set and unset any time.",
1045 root 1.74 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
1046 root 1.1 );
1047    
1048 root 1.104 $table->add (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1049     $table->add (1, 9, new CFPlus::UI::Entry
1050 root 1.1 text => $CFG->{output_count},
1051     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1052 root 1.74 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
1053 root 1.1 );
1054    
1055 root 1.104 $table->add (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1056     $table->add (1, 10, new CFPlus::UI::Entry
1057 root 1.1 text => $CFG->{output_sync},
1058     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1059 root 1.74 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
1060 root 1.1 );
1061    
1062 root 1.104 $table->add (1, 11, $LOGIN_BUTTON = new CFPlus::UI::Button
1063 root 1.1 expand => 1,
1064     align => 0,
1065     text => "Login",
1066 root 1.18 on_activate => sub {
1067 root 1.1 $CONN ? stop_game
1068     : start_game;
1069 root 1.74 0
1070 root 1.1 },
1071     );
1072    
1073 root 1.104 $table->add (0, 12, new CFPlus::UI::Label valign => 0, align => 1, text => "Chat Command");
1074     $table->add (1, 12, my $saycmd = new CFPlus::UI::Entry
1075 root 1.119 text => $CFG->{say_command},
1076 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. "
1077     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
1078     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
1079     on_changed => sub {
1080     my ($self, $value) = @_;
1081     $CFG->{say_command} = $value;
1082 root 1.74 0
1083 root 1.49 }
1084     );
1085    
1086 root 1.119 $table->add (0, 13, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1087     $table->add (1, 13, my $saycmd = new CFPlus::UI::CheckBox
1088     state => $CFG->{show_tips},
1089     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1090     on_changed => sub {
1091     my ($self, $value) = @_;
1092 root 1.121 $CFG->{show_tips} = $value;
1093 root 1.119 0
1094     }
1095     );
1096    
1097 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1098     label => "Server Info",
1099     child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1100 root 1.67 );
1101    
1102 root 1.49 $vbox
1103 root 1.1 }
1104    
1105     sub message_window {
1106 root 1.114 my $window = new CFPlus::UI::Toplevel
1107 elmex 1.16 name => "message_window",
1108 root 1.1 title => "Messages",
1109     border_bg => [1, 1, 1, 1],
1110 root 1.30 x => "max",
1111     y => 0,
1112 root 1.60 force_w => $::WIDTH * 0.4,
1113     force_h => $::HEIGHT * 0.5,
1114 root 1.104 child => (my $vbox = new CFPlus::UI::VBox),
1115 root 1.74 has_close_button => 1;
1116 root 1.1
1117     $vbox->add ($LOGVIEW);
1118    
1119 root 1.104 $vbox->add (my $input = new CFPlus::UI::Entry
1120 root 1.1 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
1121     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
1122     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
1123     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
1124 root 1.18 on_focus_in => sub {
1125 root 1.1 my ($input, $prev_focus) = @_;
1126    
1127     delete $input->{refocus_map};
1128    
1129     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
1130     $input->{refocus_map} = 1;
1131     }
1132     delete $input->{auto_activated};
1133 root 1.74
1134     0
1135 root 1.1 },
1136 root 1.18 on_activate => sub {
1137 root 1.1 my ($input, $text) = @_;
1138     $input->set_text ('');
1139    
1140 elmex 1.46 if ($text =~ /^\/(.*)/) {
1141 root 1.1 $::CONN->user_send ($1);
1142     } else {
1143     my $say_cmd = $::CFG->{say_command} || 'say';
1144     $::CONN->user_send ("$say_cmd $text");
1145     }
1146     if ($input->{refocus_map}) {
1147     delete $input->{refocus_map};
1148     $MAPWIDGET->focus_in
1149     }
1150 root 1.74
1151     0
1152 root 1.1 },
1153 root 1.18 on_escape => sub {
1154 root 1.74 $MAPWIDGET->grab_focus;
1155    
1156     0
1157 root 1.1 },
1158     );
1159    
1160     $CONSOLE = {
1161     window => $window,
1162 root 1.30 input => $input,
1163 root 1.1 };
1164    
1165     $window
1166     }
1167    
1168 root 1.49 sub autopickup_setup {
1169 root 1.104 my $table = new CFPlus::UI::Table;
1170 elmex 1.44
1171 elmex 1.43 for (
1172 root 1.51 ["General", 0, 0,
1173 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1174 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1175     ["Stop before pickup" => PICKUP_STOP],
1176     ["Debug autopickup" => PICKUP_DEBUG],
1177 root 1.51 ],
1178     ["Weapons", 0, 6,
1179 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1180     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1181     ["Bows" => PICKUP_BOW],
1182     ["Arrows" => PICKUP_ARROW],
1183 root 1.51 ],
1184     ["Armour", 0, 12,
1185 root 1.58 ["Helmets" => PICKUP_HELMET],
1186     ["Shields" => PICKUP_SHIELD],
1187     ["Body Armour" => PICKUP_ARMOUR],
1188     ["Boots" => PICKUP_BOOTS],
1189     ["Gloves" => PICKUP_GLOVES],
1190     ["Cloaks" => PICKUP_CLOAK],
1191 root 1.51 ],
1192    
1193     ["Readables", 2, 2,
1194 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1195     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1196     ["Normal Books/Scrolls" => PICKUP_READABLES],
1197 root 1.51 ],
1198     ["Misc", 2, 7,
1199 root 1.58 ["Food" => PICKUP_FOOD],
1200     ["Drinks" => PICKUP_DRINK],
1201     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1202     ["Keys" => PICKUP_KEY],
1203     ["Magical Items" => PICKUP_MAGICAL],
1204     ["Potions" => PICKUP_POTION],
1205     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1206     ["Ignore cursed" => PICKUP_NOT_CURSED],
1207     ["Jewelery" => PICKUP_JEWELS],
1208 root 1.51 ],
1209 elmex 1.66 ["Weight/Value ratio", 2, 17]
1210 elmex 1.43 )
1211     {
1212 root 1.51 my ($title, $x, $y, @bits) = @$_;
1213 root 1.104 $table->add ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1214 root 1.51
1215     for (@bits) {
1216     ++$y;
1217    
1218 elmex 1.43 my $mask = $_->[1];
1219 root 1.104 $table->add ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1220     $table->add ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1221 elmex 1.83 state => $::CFG->{pickup} & $mask,
1222 elmex 1.43 on_changed => sub {
1223     my ($box, $value) = @_;
1224 root 1.63
1225 elmex 1.43 if ($value) {
1226 elmex 1.45 $::CFG->{pickup} |= $mask;
1227 elmex 1.43 } else {
1228 root 1.63 $::CFG->{pickup} &= ~$mask;
1229 elmex 1.43 }
1230 root 1.63
1231     $::CONN->send_command ("pickup $::CFG->{pickup}")
1232 elmex 1.45 if defined $::CONN;
1233 root 1.74
1234     0
1235 elmex 1.43 });
1236 root 1.86
1237     ${$_->[2]} = $checkbox if $_->[2];
1238 elmex 1.43 }
1239     }
1240    
1241 root 1.104 $table->add (2, 18, new CFPlus::UI::ValSlider
1242 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1243     template => ">= 99",
1244 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1245     on_changed => sub {
1246     my ($slider, $value) = @_;
1247    
1248 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1249 elmex 1.66 $::CFG->{pickup} |= int $value
1250     if $value;
1251     1;
1252     });
1253 elmex 1.83
1254 root 1.104 $table->add (3, 18, new CFPlus::UI::Button
1255 elmex 1.66 text => "set",
1256     on_activate => sub {
1257     $::CONN->send_command ("pickup $::CFG->{pickup}")
1258     if defined $::CONN;
1259 root 1.74 0
1260 elmex 1.66 });
1261    
1262 root 1.51 $table
1263 elmex 1.43 }
1264    
1265 root 1.102 my %SORT_ORDER = (
1266     type => undef,
1267     mtime => sub { sort {
1268     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1269     or $b->{mtime} <=> $a->{mtime}
1270     or $a->{type} <=> $b->{type}
1271     } @_ },
1272     weight => sub { sort {
1273     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1274     or $a->{type} <=> $b->{type}
1275     } @_ },
1276     );
1277    
1278 elmex 1.85 sub inventory_widget {
1279 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1280 root 1.1
1281 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1282     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1283 root 1.99
1284 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1285 root 1.99
1286     use sort 'stable';
1287    
1288 root 1.104 $hb1->add (new CFPlus::UI::Selector
1289 root 1.102 value => $::CFG->{inv_sort},
1290 root 1.99 options => [
1291 root 1.102 [type => "Type/Name"],
1292     [mtime => "Recent/Normal/Locked"],
1293     [weight => "Weight/Type"],
1294 root 1.99 ],
1295     on_changed => sub {
1296 root 1.102 $::CFG->{inv_sort} = $_[1];
1297     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1298 root 1.99 },
1299     );
1300 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1301 root 1.99 #TODO# update to weigh/maxweight
1302 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1303 root 1.99
1304 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1305     $sw1->add ($INV = new CFPlus::UI::Inventory);
1306 root 1.1
1307 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1308 elmex 1.17
1309 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1310 elmex 1.14
1311 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1312     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1313 root 1.1
1314 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1315 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1316 elmex 1.27
1317 elmex 1.85 $hb
1318 root 1.1 }
1319    
1320 root 1.86 sub toggle_player_page {
1321     my ($widget) = @_;
1322    
1323     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1324     $PL_WINDOW->hide;
1325     } else {
1326     $PL_NOTEBOOK->set_current_page ($widget);
1327     $PL_WINDOW->show;
1328     }
1329     }
1330    
1331 elmex 1.85 sub player_window {
1332 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1333 elmex 1.85 x => "center",
1334     y => "center",
1335     force_w => $WIDTH * 9/10,
1336     force_h => $HEIGHT * 9/10,
1337     title => "Player",
1338 elmex 1.90 name => "playerbook",
1339 elmex 1.85 has_close_button => 1
1340     ;
1341    
1342     my $ntb =
1343     $PL_NOTEBOOK =
1344 root 1.104 new CFPlus::UI::Notebook expand => 1, debug => 1;
1345 root 1.86
1346 elmex 1.85 $ntb->add (
1347 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1348 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1349     );
1350     $ntb->add (
1351 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1352 elmex 1.92 "Shows all your Skills."
1353 elmex 1.85 );
1354 elmex 1.97
1355 root 1.104 my $spellsw = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1356     $spellsw->add ($SPELL_PAGE = new CFPlus::UI::SpellList);
1357 elmex 1.85 $ntb->add (
1358 elmex 1.97 "Spellbook (F4)" => $spellsw,
1359 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1360 elmex 1.85 );
1361     $ntb->add (
1362 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1363 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1364     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1365 elmex 1.85 );
1366    
1367 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1368 root 1.86
1369 elmex 1.85 $plwin->add ($ntb);
1370     $plwin
1371 elmex 1.38 }
1372    
1373 elmex 1.77 sub update_bindings {
1374     $BIND_UPD_CB->() if $BIND_UPD_CB;
1375     }
1376    
1377 root 1.49 sub keyboard_setup {
1378 root 1.104 my $binding_list = new CFPlus::UI::VBox;
1379 elmex 1.24
1380 elmex 1.34 my $refresh;
1381 elmex 1.77 $refresh = $BIND_UPD_CB = sub {
1382 elmex 1.24 $binding_list->clear ();
1383    
1384 root 1.120 return unless $PROFILE;
1385    
1386     for my $mod (keys %{$PROFILE->{bindings}}) {
1387     for my $sym (keys %{$PROFILE->{bindings}{$mod}}) {
1388     my $cmds = $PROFILE->{bindings}{$mod}{$sym};
1389 elmex 1.24 next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1390    
1391     my $lbl = join "; ", @$cmds;
1392 root 1.104 my $nam = CFPlus::BindingEditor::keycombo_to_name ($mod, $sym);
1393     $binding_list->add (my $hb = new CFPlus::UI::HBox);
1394     $hb->add (new CFPlus::UI::Button
1395 elmex 1.25 text => "delete",
1396 elmex 1.34 tooltip => "Deletes the binding",
1397 elmex 1.24 on_activate => sub {
1398     $binding_list->remove ($hb);
1399 root 1.120 delete $PROFILE->{bindings}{$mod}{$sym};
1400 root 1.74 0
1401 elmex 1.24 });
1402 elmex 1.34
1403 root 1.104 $hb->add (new CFPlus::UI::Button
1404 elmex 1.34 text => "edit",
1405     tooltip => "Edits the binding",
1406     on_activate => sub {
1407     $::BIND_EDITOR->set_binding (
1408 root 1.120 $mod, $sym, $PROFILE->{bindings}{$mod}{$sym},
1409 elmex 1.34 sub {
1410     my ($nmod, $nsym, $ncmds) = @_;
1411 elmex 1.77 $::BIND_EDITOR->cfg_unbind ($mod, $sym);
1412     $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds);
1413 elmex 1.34 $refresh->();
1414 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1415     $SETUP_DIALOG->show;
1416 elmex 1.34 },
1417     sub {
1418 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1419     $SETUP_DIALOG->show;
1420 elmex 1.34 });
1421     $::BIND_EDITOR->show;
1422 root 1.49 $SETUP_DIALOG->hide;
1423 root 1.74 0
1424 elmex 1.34 });
1425    
1426 root 1.104 $hb->add (new CFPlus::UI::Label text => "(Key: $nam)");
1427     $hb->add (new CFPlus::UI::Label text => $lbl, expand => 1);
1428 elmex 1.24 }
1429     }
1430     };
1431    
1432 root 1.104 my $vb = new CFPlus::UI::VBox;
1433 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1434     label => "Options",
1435     child => (my $hb = new CFPlus::UI::HBox),
1436     );
1437 root 1.104 $hb->add (new CFPlus::UI::Label text => "only shift-up stops fire");
1438     $hb->add (new CFPlus::UI::CheckBox
1439 elmex 1.71 expand => 1,
1440     state => $CFG->{shift_fire_stop},
1441     tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift",
1442     on_changed => sub {
1443     my ($cbox, $value) = @_;
1444     $CFG->{shift_fire_stop} = $value;
1445 root 1.74 0
1446 elmex 1.71 });
1447    
1448 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1449     label => "Bindings",
1450     child => $binding_list);
1451 root 1.104 $vb->add (my $hb = new CFPlus::UI::HBox);
1452 root 1.49
1453 root 1.104 $hb->add (new CFPlus::UI::Button
1454 elmex 1.34 text => "record new",
1455 elmex 1.35 expand => 1,
1456 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1457     on_activate => sub {
1458     $::BIND_EDITOR->set_binding (undef, undef, [],
1459     sub {
1460     my ($mod, $sym, $cmds) = @_;
1461 elmex 1.77 $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds);
1462 elmex 1.34 $refresh->();
1463 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1464     $SETUP_DIALOG->show;
1465 elmex 1.34 },
1466     sub {
1467 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1468     $SETUP_DIALOG->show;
1469 root 1.53 },
1470     );
1471 root 1.49 $SETUP_DIALOG->hide;
1472 elmex 1.34 $::BIND_EDITOR->show;
1473 root 1.74 0
1474 elmex 1.34 },
1475     );
1476 root 1.49
1477 root 1.104 $hb->add (new CFPlus::UI::Button
1478 elmex 1.35 text => "close",
1479     tooltip => "Closes the binding window",
1480     expand => 1,
1481     on_activate => sub {
1482 root 1.49 $SETUP_DIALOG->hide;
1483 root 1.74 0
1484 elmex 1.35 }
1485     );
1486    
1487 elmex 1.24 $refresh->();
1488 root 1.49
1489     $vb
1490 elmex 1.24 }
1491    
1492 root 1.64 sub help_window {
1493 root 1.114 my $win = new CFPlus::UI::Toplevel
1494 root 1.41 x => 'center',
1495     y => 'center',
1496 root 1.119 z => 4,
1497 root 1.41 name => 'doc_browser',
1498     force_w => int $WIDTH * 7/8,
1499     force_h => int $HEIGHT * 7/8,
1500 root 1.87 title => "Help Browser",
1501     has_close_button => 1;
1502 root 1.1
1503 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1504 root 1.1
1505 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1506     label => "Navigation",
1507     child => (my $buttons = new CFPlus::UI::HBox),
1508     );
1509 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1510 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1511 root 1.1
1512 root 1.111 my @history;
1513     my @future;
1514 root 1.112 my $curnode;
1515 root 1.111
1516     my $load_node; $load_node = sub {
1517 root 1.115 my ($node, $para) = @_;
1518 root 1.111
1519     $buttons->clear;
1520    
1521 root 1.116 $buttons->add (new CFPlus::UI::Button
1522     text => "⇤",
1523     tooltip => "back to the starting page",
1524     on_activate => sub {
1525     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1526     unshift @future, @history;
1527     @history = ();
1528     $load_node->(@{shift @future});
1529     },
1530     );
1531    
1532 root 1.111 if (@history) {
1533 root 1.112 $buttons->add (new CFPlus::UI::Button
1534 root 1.113 text => "⋘",
1535 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1536 root 1.112 on_activate => sub {
1537 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1538     $load_node->(@{pop @history});
1539 root 1.113 },
1540     );
1541 root 1.112 }
1542 root 1.111
1543 root 1.112 if (@future) {
1544     $buttons->add (new CFPlus::UI::Button
1545 root 1.113 text => "â‹™",
1546 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1547 root 1.112 on_activate => sub {
1548 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1549     $load_node->(@{shift @future});
1550 root 1.113 },
1551     );
1552 root 1.111 }
1553 root 1.112
1554 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1555    
1556     my @path = CFPlus::Pod::full_path_of $node;
1557     pop @path; # drop current node
1558    
1559     for my $node (@path) {
1560     $buttons->add (new CFPlus::UI::Button
1561     text => $node->{kw}[0],
1562     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1563     on_activate => sub {
1564 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1565 root 1.113 $load_node->($node);
1566     },
1567     );
1568     $buttons->add (new CFPlus::UI::Label text => "/");
1569     }
1570    
1571 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1572 root 1.113
1573 root 1.112 $curnode = $node;
1574    
1575 root 1.111 $viewer->clear;
1576 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1577 root 1.115 $viewer->scroll_to ($para);
1578 root 1.111 };
1579    
1580     $load_node->(CFPlus::Pod::find pod => "mainpage");
1581    
1582 root 1.113 $CFPlus::Pod::goto_document = sub {
1583 root 1.111 my (@path) = @_;
1584    
1585 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1586 root 1.111
1587 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1588 root 1.111 $win->show;
1589     };
1590 root 1.1
1591     $win
1592     }
1593    
1594 root 1.119 sub open_string_query {
1595     my ($title, $cb, $txt, $tooltip) = @_;
1596     my $dialog = new CFPlus::UI::Toplevel
1597     x => "center",
1598     y => "center",
1599     z => 50,
1600     force_w => $WIDTH * 4/5,
1601     title => $title;
1602    
1603     $dialog->add (
1604     my $e = new CFPlus::UI::Entry
1605     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1606     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1607     tooltip => $tooltip
1608     );
1609    
1610     $e->grab_focus;
1611     $e->set_text ($txt) if $txt;
1612     $dialog->show;
1613     }
1614    
1615     sub open_quit_dialog {
1616     unless ($QUIT_DIALOG) {
1617     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1618     x => "center",
1619     y => "center",
1620     z => 50,
1621     title => "Really Quit?",
1622     on_key_down => sub {
1623     my ($dialog, $ev) = @_;
1624     $ev->{sym} == 27 and $dialog->hide;
1625     }
1626     ;
1627    
1628     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1629    
1630     $vb->add (new CFPlus::UI::Label
1631     text => "You should find a savebed and apply it first!",
1632     max_w => $WIDTH * 0.25,
1633     ellipsize => 0,
1634     );
1635     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1636     $hb->add (new CFPlus::UI::Button
1637     text => "Ok",
1638     expand => 1,
1639     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1640     );
1641     $hb->add (new CFPlus::UI::Button
1642     text => "Quit anyway",
1643     expand => 1,
1644     on_activate => sub { exit },
1645     );
1646     }
1647    
1648     $QUIT_DIALOG->show;
1649     $QUIT_DIALOG->grab_focus;
1650     }
1651    
1652     sub show_tip_of_the_day {
1653     # find all tips
1654     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1655    
1656     my $todindex = $CFPlus::DB_STATE->get ("tip_of_the_day");
1657     $todindex = 0 if $todindex >= @tod;
1658     $CFPlus::DB_STATE->put (tip_of_the_day => $todindex + 1);
1659    
1660     # create dialog
1661     my $dialog;
1662    
1663     my $close = sub {
1664     $dialog->destroy;
1665     };
1666    
1667     $dialog = new CFPlus::UI::Toplevel
1668     x => "center",
1669     y => "center",
1670     z => 3,
1671     name => 'tip_of_the_day',
1672     force_w => int $WIDTH * 4/9,
1673     force_h => int $WIDTH * 2/9,
1674     title => "Tip of the day #" . (1 + $todindex),
1675     child => my $vbox = new CFPlus::UI::VBox,
1676     has_close_button => 1,
1677     on_delete => $close,
1678     ;
1679    
1680     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1681     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1682     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1683    
1684     $vbox->add (my $table = new CFPlus::UI::Table);
1685    
1686     $table->add (0, 0, new CFPlus::UI::Button
1687     text => "Close",
1688     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>.",
1689     on_activate => $close,
1690     );
1691    
1692     $table->add (2, 0, new CFPlus::UI::Button
1693     text => "Next",
1694     tooltip => "Show the next <b>Tip of the day</b>.",
1695     on_activate => sub {
1696     $close->();
1697     &show_tip_of_the_day;
1698     },
1699     );
1700    
1701     $dialog->show;
1702     }
1703    
1704 root 1.1 sub sdl_init {
1705 root 1.104 CFPlus::SDL_Init
1706 root 1.1 and die "SDL::Init failed!\n";
1707     }
1708    
1709     sub video_init {
1710     sdl_init;
1711    
1712     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1713    
1714     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1715    
1716     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1717     $FULLSCREEN = $CFG->{fullscreen};
1718     $FAST = $CFG->{fast};
1719    
1720 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1721     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1722 root 1.1
1723     $SDL_ACTIVE = 1;
1724     $LAST_REFRESH = time - 0.01;
1725    
1726 root 1.104 CFPlus::OpenGL::init;
1727 root 1.1
1728     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1729    
1730 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1731 root 1.1
1732     #############################################################################
1733    
1734     if ($DEBUG_STATUS) {
1735 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1736 root 1.1 } else {
1737     # create the widgets
1738    
1739 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1740 root 1.30 padding => 0,
1741     z => 100,
1742     force_x => "max",
1743     force_y => 0;
1744 root 1.1 $DEBUG_STATUS->show;
1745 elmex 1.34
1746 root 1.104 $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0);
1747 elmex 1.34
1748 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1749 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1750 root 1.1
1751 root 1.104 (new CFPlus::UI::Frame
1752 root 1.1 bg => [0, 0, 0, 0.4],
1753 root 1.30 force_x => 0,
1754     force_y => "max",
1755 root 1.1 child => $STATUSBOX,
1756     )->show;
1757    
1758 root 1.114 CFPlus::UI::Toplevel->new (
1759 root 1.47 title => "Map",
1760 root 1.42 name => "mapmap",
1761 root 1.30 x => 0,
1762     y => $FONTSIZE + 8,
1763 root 1.1 border_bg => [1, 1, 1, 192/255],
1764     bg => [1, 1, 1, 0],
1765 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1766 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1767     ),
1768     )->show;
1769    
1770 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1771 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1772     my ($mapwidget, $preset) = @_;
1773    
1774     if ($CONSOLE) {
1775     $CONSOLE->{input}->{auto_activated} = 1;
1776 root 1.74 $CONSOLE->{input}->grab_focus;
1777 root 1.1
1778     if ($preset && $CONSOLE->{input}->get_text eq '') {
1779     $CONSOLE->{input}->set_text ($preset);
1780     }
1781     }
1782     });
1783     $MAPWIDGET->show;
1784 root 1.74 $MAPWIDGET->grab_focus;
1785 root 1.1
1786 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1787 root 1.1 expand => 1,
1788     font => $FONT_FIXED,
1789     fontsize => $::CFG->{log_fontsize},
1790 root 1.61 indent => -4,
1791 root 1.1 can_hover => 1,
1792     can_events => 1,
1793     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1794     ;
1795    
1796 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1797 root 1.49 title => "Setup",
1798     name => "setup_dialog",
1799     x => 'center',
1800     y => 'center',
1801 root 1.53 z => 2,
1802 root 1.49 force_w => $::WIDTH * 0.6,
1803     force_h => $::HEIGHT * 0.6,
1804 root 1.74 has_close_button => 1,
1805 root 1.49 ;
1806    
1807 elmex 1.81 $METASERVER = metaserver_dialog;
1808    
1809 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1810     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1811 root 1.49
1812     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1813     "Configure the server to play on, your username, password and other server-related options.");
1814     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1815 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1816 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1817     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1818     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1819     "Configure the use of audio, sound effects and background music.");
1820     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1821 root 1.75 "Lets you define, edit and delete key bindings."
1822     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1823 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1824 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1825 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1826     . "binding editor closes");
1827 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1828 root 1.75 "Some debuggin' options. Do not ask.");
1829 root 1.49
1830 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1831 root 1.1
1832 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1833 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1834    
1835 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1836 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1837    
1838     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
1839    
1840 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1841 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1842 root 1.1
1843 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1844 root 1.1 text => "Save Config",
1845     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1846 root 1.18 on_activate => sub {
1847 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1848     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1849 root 1.1 status "Configuration Saved";
1850 root 1.74 0
1851 root 1.1 },
1852     );
1853    
1854 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1855 root 1.1 tooltip => "View Documentation");
1856    
1857 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1858 root 1.18 text => "Quit",
1859     tooltip => "Terminates the program",
1860     on_activate => sub {
1861 root 1.1 if ($CONN) {
1862     open_quit_dialog;
1863     } else {
1864     exit;
1865     }
1866 root 1.74 0
1867 root 1.1 },
1868     );
1869    
1870     $BUTTONBAR->show;
1871 root 1.49 $SETUP_DIALOG->show;
1872     }
1873 root 1.1
1874 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1875 root 1.1 }
1876    
1877     sub video_shutdown {
1878 root 1.104 CFPlus::OpenGL::shutdown;
1879 root 1.73
1880 root 1.1 undef $SDL_ACTIVE;
1881     }
1882    
1883     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1884     my $bgmusic;#TODO#hack#d#
1885    
1886     sub audio_channel_finished {
1887     my ($channel) = @_;
1888    
1889     #warn "channel $channel finished\n";#d#
1890     }
1891    
1892     sub audio_music_finished {
1893     return unless $CFG->{bgm_enable};
1894    
1895     # TODO: hack, do play loop and mood music
1896 root 1.104 $bgmusic = new_from_file CFPlus::MixMusic CFPlus::find_rcfile "music/$bgmusic[0]";
1897 root 1.1 $bgmusic->play (0);
1898    
1899     push @bgmusic, shift @bgmusic;
1900     }
1901    
1902     sub audio_init {
1903     if ($CFG->{audio_enable}) {
1904 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1905     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1906 root 1.1
1907     unless ($SDL_MIXER) {
1908     status "Unable to open sound device: there will be no sound";
1909     return;
1910     }
1911    
1912 root 1.104 CFPlus::Mix_AllocateChannels 8;
1913     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1914 root 1.1
1915     audio_music_finished;
1916    
1917     while (<$fh>) {
1918     next if /^\s*#/;
1919     next if /^\s*$/;
1920    
1921     my ($file, $volume, $event) = split /\s+/, $_, 3;
1922    
1923     push @SOUNDS, "$volume,$file";
1924    
1925     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1926 root 1.104 my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
1927 root 1.1 $chunk->volume ($volume * 128 / 100);
1928     $chunk
1929     };
1930     }
1931     } else {
1932     status "unable to open sound config: $!";
1933     }
1934     }
1935     }
1936    
1937     sub audio_shutdown {
1938 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1939 root 1.1 undef $SDL_MIXER;
1940     @SOUNDS = ();
1941     %AUDIO_CHUNKS = ();
1942     }
1943    
1944     my %animate_object;
1945     my $animate_timer;
1946    
1947     my $fps = 9;
1948    
1949     my %demo;#d#
1950    
1951     sub force_refresh {
1952     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1953 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1954 root 1.1
1955 root 1.104 $CFPlus::UI::ROOT->draw;
1956 root 1.1
1957     $WANT_REFRESH = 0;
1958     $CAN_REFRESH = 0;
1959     $LAST_REFRESH = $NOW;
1960    
1961 root 1.104 CFPlus::SDL_GL_SwapBuffers;
1962 root 1.1 }
1963    
1964 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1965 root 1.1 $NOW = time;
1966    
1967     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1968 root 1.104 for CFPlus::SDL_PollEvent;
1969 root 1.1
1970     if (%animate_object) {
1971     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1972     $WANT_REFRESH++;
1973     }
1974    
1975     if ($WANT_REFRESH) {
1976     force_refresh;
1977     } else {
1978     $CAN_REFRESH = 1;
1979     }
1980     });
1981    
1982     sub animation_start {
1983     my ($widget) = @_;
1984     $animate_object{$widget} = $widget;
1985     }
1986    
1987     sub animation_stop {
1988     my ($widget) = @_;
1989     delete $animate_object{$widget};
1990     }
1991    
1992     # check once/second for faces that need to be prefetched
1993     # this should, of course, only run on demand, but
1994     # SDL forces worse things on us....
1995    
1996     Event->timer (after => 1, interval => 0.25, cb => sub {
1997     $CONN->face_prefetch
1998     if $CONN;
1999     });
2000    
2001     %SDL_CB = (
2002 root 1.104 CFPlus::SDL_QUIT => sub {
2003 root 1.1 Event::unloop -1;
2004     },
2005 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2006 root 1.1 },
2007 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2008     CFPlus::UI::full_refresh;
2009 root 1.1 },
2010 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2011 root 1.1 # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
2012     },
2013 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2014     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2015 root 1.1 # alt-enter
2016 root 1.94 $FULLSCREEN_ENABLE->toggle;
2017 root 1.1 video_shutdown;
2018     video_init;
2019     } else {
2020 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2021 root 1.1 }
2022     },
2023 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2024     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2025     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2026     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2027     CFPlus::SDL_USEREVENT => sub {
2028 root 1.1 if ($_[0]{code} == 1) {
2029     audio_channel_finished $_[0]{data1};
2030     } elsif ($_[0]{code} == 0) {
2031     audio_music_finished;
2032     }
2033     },
2034     );
2035    
2036     #############################################################################
2037    
2038     $SIG{INT} = $SIG{TERM} = sub { exit };
2039    
2040     {
2041 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2042     CFPlus::UI::set_layout ($::CFG->{layout});
2043 root 1.1
2044     my %DEF_CFG = (
2045 root 1.75 sdl_mode => 0,
2046     width => 640,
2047     height => 480,
2048     fullscreen => 0,
2049     fast => 0,
2050     map_scale => 1,
2051     fow_enable => 1,
2052     fow_intensity => 0.45,
2053     fow_smooth => 0,
2054     gui_fontsize => 1,
2055     log_fontsize => 0.7,
2056     gauge_fontsize => 1,
2057     gauge_size => 0.35,
2058     stat_fontsize => 0.7,
2059     mapsize => 100,
2060     say_command => 'say',
2061     audio_enable => 1,
2062     bgm_enable => 1,
2063     bgm_volume => 0.25,
2064     face_prefetch => 0,
2065     output_sync => 1,
2066     output_count => 1,
2067     pickup => 0,
2068 root 1.102 inv_sort => "mtime",
2069 root 1.75 default => "profile", # default profile
2070 root 1.119 show_tips => 1,
2071 root 1.1 );
2072 root 1.75
2073 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2074     $CFG->{$k} = $v unless exists $CFG->{$k};
2075     }
2076    
2077 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2078    
2079 root 1.1 sdl_init;
2080    
2081     @SDL_MODES = reverse
2082     grep $_->[0] >= 640 && $_->[1] >= 480,
2083 root 1.104 CFPlus::SDL_ListModes;
2084 root 1.1
2085 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2086 root 1.1
2087     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2088    
2089     {
2090 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2091 root 1.1 DejaVuSans.ttf
2092     DejaVuSansMono.ttf
2093     DejaVuSans-Bold.ttf
2094     DejaVuSansMono-Bold.ttf
2095     DejaVuSans-Oblique.ttf
2096     DejaVuSansMono-Oblique.ttf
2097     DejaVuSans-BoldOblique.ttf
2098     DejaVuSansMono-BoldOblique.ttf
2099     );
2100    
2101 root 1.104 CFPlus::add_font $_ for @fonts;
2102 root 1.1
2103 root 1.104 CFPlus::pango_init;
2104 root 1.1
2105 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2106     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2107 root 1.1
2108     $FONT_PROP->make_default;
2109     }
2110    
2111     # compare mono (ft) vs. rgba (cairo)
2112     # ft - 1.8s, cairo 3s, even in alpha-only mode
2113     # for my $rgba (0..1) {
2114     # my $t1 = Time::HiRes::time;
2115     # for (1..1000) {
2116 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2117 root 1.1 # $layout->set_text ("hallo" x 100);
2118     # $layout->render;
2119     # }
2120     # my $t2 = Time::HiRes::time;
2121     # warn $t2-$t1;
2122     # }
2123    
2124 root 1.122 $startup_done->();
2125    
2126 root 1.1 video_init;
2127     audio_init;
2128     }
2129    
2130 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2131    
2132 root 1.1 Event::loop;
2133 root 1.104 #CFPlus::SDL_Quit;
2134     #CFPlus::_exit 0;
2135 root 1.1
2136 root 1.104 END { CFPlus::SDL_Quit }
2137 root 1.1
2138     =head1 NAME
2139    
2140 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2141 root 1.1
2142     =head1 SYNOPSIS
2143    
2144     Just run it - no commandline arguments are supported.
2145    
2146     =head1 USAGE
2147    
2148 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2149 root 1.1 fullscreen and interactively.
2150    
2151 root 1.39 =head1 DEBUGGING
2152    
2153    
2154     CFPLUS_DEBUG - environment variable
2155    
2156     1 draw borders around widgets
2157     2 add low-level widget info to tooltips
2158     4 show fps
2159     8 suppress tooltips
2160    
2161 root 1.1 =head1 AUTHOR
2162    
2163     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2164    
2165    
2166