ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.126
Committed: Sat Nov 18 18:19:05 2006 UTC (17 years, 6 months ago) by root
Branch: MAIN
Changes since 1.125: +1 -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 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.1
1308 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1309 elmex 1.17
1310 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1311 elmex 1.14
1312 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1313     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1314 root 1.1
1315 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1316 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1317 elmex 1.27
1318 elmex 1.85 $hb
1319 root 1.1 }
1320    
1321 root 1.86 sub toggle_player_page {
1322     my ($widget) = @_;
1323    
1324     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1325     $PL_WINDOW->hide;
1326     } else {
1327     $PL_NOTEBOOK->set_current_page ($widget);
1328     $PL_WINDOW->show;
1329     }
1330     }
1331    
1332 elmex 1.85 sub player_window {
1333 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1334 elmex 1.85 x => "center",
1335     y => "center",
1336     force_w => $WIDTH * 9/10,
1337     force_h => $HEIGHT * 9/10,
1338     title => "Player",
1339 elmex 1.90 name => "playerbook",
1340 elmex 1.85 has_close_button => 1
1341     ;
1342    
1343     my $ntb =
1344     $PL_NOTEBOOK =
1345 root 1.104 new CFPlus::UI::Notebook expand => 1, debug => 1;
1346 root 1.86
1347 elmex 1.85 $ntb->add (
1348 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1349 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1350     );
1351     $ntb->add (
1352 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1353 elmex 1.92 "Shows all your Skills."
1354 elmex 1.85 );
1355 elmex 1.97
1356 root 1.104 my $spellsw = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1357     $spellsw->add ($SPELL_PAGE = new CFPlus::UI::SpellList);
1358 elmex 1.85 $ntb->add (
1359 elmex 1.97 "Spellbook (F4)" => $spellsw,
1360 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1361 elmex 1.85 );
1362     $ntb->add (
1363 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1364 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1365     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1366 elmex 1.85 );
1367    
1368 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1369 root 1.86
1370 elmex 1.85 $plwin->add ($ntb);
1371     $plwin
1372 elmex 1.38 }
1373    
1374 elmex 1.77 sub update_bindings {
1375     $BIND_UPD_CB->() if $BIND_UPD_CB;
1376     }
1377    
1378 root 1.49 sub keyboard_setup {
1379 root 1.104 my $binding_list = new CFPlus::UI::VBox;
1380 elmex 1.24
1381 elmex 1.34 my $refresh;
1382 elmex 1.77 $refresh = $BIND_UPD_CB = sub {
1383 elmex 1.24 $binding_list->clear ();
1384    
1385 root 1.120 return unless $PROFILE;
1386    
1387     for my $mod (keys %{$PROFILE->{bindings}}) {
1388     for my $sym (keys %{$PROFILE->{bindings}{$mod}}) {
1389     my $cmds = $PROFILE->{bindings}{$mod}{$sym};
1390 elmex 1.24 next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1391    
1392     my $lbl = join "; ", @$cmds;
1393 root 1.104 my $nam = CFPlus::BindingEditor::keycombo_to_name ($mod, $sym);
1394     $binding_list->add (my $hb = new CFPlus::UI::HBox);
1395     $hb->add (new CFPlus::UI::Button
1396 elmex 1.25 text => "delete",
1397 elmex 1.34 tooltip => "Deletes the binding",
1398 elmex 1.24 on_activate => sub {
1399     $binding_list->remove ($hb);
1400 root 1.120 delete $PROFILE->{bindings}{$mod}{$sym};
1401 root 1.74 0
1402 elmex 1.24 });
1403 elmex 1.34
1404 root 1.104 $hb->add (new CFPlus::UI::Button
1405 elmex 1.34 text => "edit",
1406     tooltip => "Edits the binding",
1407     on_activate => sub {
1408     $::BIND_EDITOR->set_binding (
1409 root 1.120 $mod, $sym, $PROFILE->{bindings}{$mod}{$sym},
1410 elmex 1.34 sub {
1411     my ($nmod, $nsym, $ncmds) = @_;
1412 elmex 1.77 $::BIND_EDITOR->cfg_unbind ($mod, $sym);
1413     $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds);
1414 elmex 1.34 $refresh->();
1415 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1416     $SETUP_DIALOG->show;
1417 elmex 1.34 },
1418     sub {
1419 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1420     $SETUP_DIALOG->show;
1421 elmex 1.34 });
1422     $::BIND_EDITOR->show;
1423 root 1.49 $SETUP_DIALOG->hide;
1424 root 1.74 0
1425 elmex 1.34 });
1426    
1427 root 1.104 $hb->add (new CFPlus::UI::Label text => "(Key: $nam)");
1428     $hb->add (new CFPlus::UI::Label text => $lbl, expand => 1);
1429 elmex 1.24 }
1430     }
1431     };
1432    
1433 root 1.104 my $vb = new CFPlus::UI::VBox;
1434 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1435     label => "Options",
1436     child => (my $hb = new CFPlus::UI::HBox),
1437     );
1438 root 1.104 $hb->add (new CFPlus::UI::Label text => "only shift-up stops fire");
1439     $hb->add (new CFPlus::UI::CheckBox
1440 elmex 1.71 expand => 1,
1441     state => $CFG->{shift_fire_stop},
1442     tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift",
1443     on_changed => sub {
1444     my ($cbox, $value) = @_;
1445     $CFG->{shift_fire_stop} = $value;
1446 root 1.74 0
1447 elmex 1.71 });
1448    
1449 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1450     label => "Bindings",
1451     child => $binding_list);
1452 root 1.104 $vb->add (my $hb = new CFPlus::UI::HBox);
1453 root 1.49
1454 root 1.104 $hb->add (new CFPlus::UI::Button
1455 elmex 1.34 text => "record new",
1456 elmex 1.35 expand => 1,
1457 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1458     on_activate => sub {
1459     $::BIND_EDITOR->set_binding (undef, undef, [],
1460     sub {
1461     my ($mod, $sym, $cmds) = @_;
1462 elmex 1.77 $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds);
1463 elmex 1.34 $refresh->();
1464 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1465     $SETUP_DIALOG->show;
1466 elmex 1.34 },
1467     sub {
1468 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1469     $SETUP_DIALOG->show;
1470 root 1.53 },
1471     );
1472 root 1.49 $SETUP_DIALOG->hide;
1473 elmex 1.34 $::BIND_EDITOR->show;
1474 root 1.74 0
1475 elmex 1.34 },
1476     );
1477 root 1.49
1478 root 1.104 $hb->add (new CFPlus::UI::Button
1479 elmex 1.35 text => "close",
1480     tooltip => "Closes the binding window",
1481     expand => 1,
1482     on_activate => sub {
1483 root 1.49 $SETUP_DIALOG->hide;
1484 root 1.74 0
1485 elmex 1.35 }
1486     );
1487    
1488 elmex 1.24 $refresh->();
1489 root 1.49
1490     $vb
1491 elmex 1.24 }
1492    
1493 root 1.64 sub help_window {
1494 root 1.114 my $win = new CFPlus::UI::Toplevel
1495 root 1.41 x => 'center',
1496     y => 'center',
1497 root 1.119 z => 4,
1498 root 1.41 name => 'doc_browser',
1499     force_w => int $WIDTH * 7/8,
1500     force_h => int $HEIGHT * 7/8,
1501 root 1.87 title => "Help Browser",
1502     has_close_button => 1;
1503 root 1.1
1504 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1505 root 1.1
1506 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1507     label => "Navigation",
1508     child => (my $buttons = new CFPlus::UI::HBox),
1509     );
1510 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1511 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1512 root 1.1
1513 root 1.111 my @history;
1514     my @future;
1515 root 1.112 my $curnode;
1516 root 1.111
1517     my $load_node; $load_node = sub {
1518 root 1.115 my ($node, $para) = @_;
1519 root 1.111
1520     $buttons->clear;
1521    
1522 root 1.116 $buttons->add (new CFPlus::UI::Button
1523     text => "⇤",
1524     tooltip => "back to the starting page",
1525     on_activate => sub {
1526     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1527     unshift @future, @history;
1528     @history = ();
1529     $load_node->(@{shift @future});
1530     },
1531     );
1532    
1533 root 1.111 if (@history) {
1534 root 1.112 $buttons->add (new CFPlus::UI::Button
1535 root 1.113 text => "⋘",
1536 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1537 root 1.112 on_activate => sub {
1538 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1539     $load_node->(@{pop @history});
1540 root 1.113 },
1541     );
1542 root 1.112 }
1543 root 1.111
1544 root 1.112 if (@future) {
1545     $buttons->add (new CFPlus::UI::Button
1546 root 1.113 text => "â‹™",
1547 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1548 root 1.112 on_activate => sub {
1549 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1550     $load_node->(@{shift @future});
1551 root 1.113 },
1552     );
1553 root 1.111 }
1554 root 1.112
1555 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1556    
1557     my @path = CFPlus::Pod::full_path_of $node;
1558     pop @path; # drop current node
1559    
1560     for my $node (@path) {
1561     $buttons->add (new CFPlus::UI::Button
1562     text => $node->{kw}[0],
1563     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1564     on_activate => sub {
1565 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1566 root 1.113 $load_node->($node);
1567     },
1568     );
1569     $buttons->add (new CFPlus::UI::Label text => "/");
1570     }
1571    
1572 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1573 root 1.113
1574 root 1.112 $curnode = $node;
1575    
1576 root 1.111 $viewer->clear;
1577 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1578 root 1.115 $viewer->scroll_to ($para);
1579 root 1.111 };
1580    
1581     $load_node->(CFPlus::Pod::find pod => "mainpage");
1582    
1583 root 1.113 $CFPlus::Pod::goto_document = sub {
1584 root 1.111 my (@path) = @_;
1585    
1586 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1587 root 1.111
1588 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1589 root 1.111 $win->show;
1590     };
1591 root 1.1
1592     $win
1593     }
1594    
1595 root 1.119 sub open_string_query {
1596     my ($title, $cb, $txt, $tooltip) = @_;
1597     my $dialog = new CFPlus::UI::Toplevel
1598     x => "center",
1599     y => "center",
1600     z => 50,
1601     force_w => $WIDTH * 4/5,
1602     title => $title;
1603    
1604     $dialog->add (
1605     my $e = new CFPlus::UI::Entry
1606     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1607     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1608     tooltip => $tooltip
1609     );
1610    
1611     $e->grab_focus;
1612     $e->set_text ($txt) if $txt;
1613     $dialog->show;
1614     }
1615    
1616     sub open_quit_dialog {
1617     unless ($QUIT_DIALOG) {
1618     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1619     x => "center",
1620     y => "center",
1621     z => 50,
1622     title => "Really Quit?",
1623     on_key_down => sub {
1624     my ($dialog, $ev) = @_;
1625     $ev->{sym} == 27 and $dialog->hide;
1626     }
1627     ;
1628    
1629     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1630    
1631     $vb->add (new CFPlus::UI::Label
1632     text => "You should find a savebed and apply it first!",
1633     max_w => $WIDTH * 0.25,
1634     ellipsize => 0,
1635     );
1636     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1637     $hb->add (new CFPlus::UI::Button
1638     text => "Ok",
1639     expand => 1,
1640     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1641     );
1642     $hb->add (new CFPlus::UI::Button
1643     text => "Quit anyway",
1644     expand => 1,
1645     on_activate => sub { exit },
1646     );
1647     }
1648    
1649     $QUIT_DIALOG->show;
1650     $QUIT_DIALOG->grab_focus;
1651     }
1652    
1653     sub show_tip_of_the_day {
1654     # find all tips
1655     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1656    
1657     my $todindex = $CFPlus::DB_STATE->get ("tip_of_the_day");
1658     $todindex = 0 if $todindex >= @tod;
1659     $CFPlus::DB_STATE->put (tip_of_the_day => $todindex + 1);
1660    
1661     # create dialog
1662     my $dialog;
1663    
1664     my $close = sub {
1665     $dialog->destroy;
1666     };
1667    
1668     $dialog = new CFPlus::UI::Toplevel
1669     x => "center",
1670     y => "center",
1671     z => 3,
1672     name => 'tip_of_the_day',
1673     force_w => int $WIDTH * 4/9,
1674     force_h => int $WIDTH * 2/9,
1675     title => "Tip of the day #" . (1 + $todindex),
1676     child => my $vbox = new CFPlus::UI::VBox,
1677     has_close_button => 1,
1678     on_delete => $close,
1679     ;
1680    
1681     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1682     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1683     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1684    
1685     $vbox->add (my $table = new CFPlus::UI::Table);
1686    
1687     $table->add (0, 0, new CFPlus::UI::Button
1688     text => "Close",
1689     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>.",
1690     on_activate => $close,
1691     );
1692    
1693     $table->add (2, 0, new CFPlus::UI::Button
1694     text => "Next",
1695     tooltip => "Show the next <b>Tip of the day</b>.",
1696     on_activate => sub {
1697     $close->();
1698     &show_tip_of_the_day;
1699     },
1700     );
1701    
1702     $dialog->show;
1703     }
1704    
1705 root 1.1 sub sdl_init {
1706 root 1.104 CFPlus::SDL_Init
1707 root 1.1 and die "SDL::Init failed!\n";
1708     }
1709    
1710     sub video_init {
1711     sdl_init;
1712    
1713     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1714    
1715     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1716    
1717     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1718     $FULLSCREEN = $CFG->{fullscreen};
1719     $FAST = $CFG->{fast};
1720    
1721 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1722     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1723 root 1.1
1724     $SDL_ACTIVE = 1;
1725     $LAST_REFRESH = time - 0.01;
1726    
1727 root 1.104 CFPlus::OpenGL::init;
1728 root 1.1
1729     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1730    
1731 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1732 root 1.1
1733     #############################################################################
1734    
1735     if ($DEBUG_STATUS) {
1736 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1737 root 1.1 } else {
1738     # create the widgets
1739    
1740 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1741 root 1.30 padding => 0,
1742     z => 100,
1743     force_x => "max",
1744     force_y => 0;
1745 root 1.1 $DEBUG_STATUS->show;
1746 elmex 1.34
1747 root 1.104 $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0);
1748 elmex 1.34
1749 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1750 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1751 root 1.1
1752 root 1.104 (new CFPlus::UI::Frame
1753 root 1.1 bg => [0, 0, 0, 0.4],
1754 root 1.30 force_x => 0,
1755     force_y => "max",
1756 root 1.1 child => $STATUSBOX,
1757     )->show;
1758    
1759 root 1.114 CFPlus::UI::Toplevel->new (
1760 root 1.47 title => "Map",
1761 root 1.42 name => "mapmap",
1762 root 1.30 x => 0,
1763     y => $FONTSIZE + 8,
1764 root 1.1 border_bg => [1, 1, 1, 192/255],
1765     bg => [1, 1, 1, 0],
1766 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1767 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1768     ),
1769     )->show;
1770    
1771 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1772 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1773     my ($mapwidget, $preset) = @_;
1774    
1775     if ($CONSOLE) {
1776     $CONSOLE->{input}->{auto_activated} = 1;
1777 root 1.74 $CONSOLE->{input}->grab_focus;
1778 root 1.1
1779     if ($preset && $CONSOLE->{input}->get_text eq '') {
1780     $CONSOLE->{input}->set_text ($preset);
1781     }
1782     }
1783     });
1784     $MAPWIDGET->show;
1785 root 1.74 $MAPWIDGET->grab_focus;
1786 root 1.1
1787 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1788 root 1.1 expand => 1,
1789     font => $FONT_FIXED,
1790     fontsize => $::CFG->{log_fontsize},
1791 root 1.61 indent => -4,
1792 root 1.1 can_hover => 1,
1793     can_events => 1,
1794     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1795     ;
1796    
1797 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1798 root 1.49 title => "Setup",
1799     name => "setup_dialog",
1800     x => 'center',
1801     y => 'center',
1802 root 1.53 z => 2,
1803 root 1.49 force_w => $::WIDTH * 0.6,
1804     force_h => $::HEIGHT * 0.6,
1805 root 1.74 has_close_button => 1,
1806 root 1.49 ;
1807    
1808 elmex 1.81 $METASERVER = metaserver_dialog;
1809    
1810 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1811     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1812 root 1.49
1813     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1814     "Configure the server to play on, your username, password and other server-related options.");
1815     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1816 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1817 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1818     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1819     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1820     "Configure the use of audio, sound effects and background music.");
1821     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1822 root 1.75 "Lets you define, edit and delete key bindings."
1823     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1824 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1825 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1826 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1827     . "binding editor closes");
1828 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1829 root 1.75 "Some debuggin' options. Do not ask.");
1830 root 1.49
1831 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1832 root 1.1
1833 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1834 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1835    
1836 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1837 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1838    
1839     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
1840    
1841 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1842 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1843 root 1.1
1844 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1845 root 1.1 text => "Save Config",
1846     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1847 root 1.18 on_activate => sub {
1848 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1849     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1850 root 1.1 status "Configuration Saved";
1851 root 1.74 0
1852 root 1.1 },
1853     );
1854    
1855 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1856 root 1.1 tooltip => "View Documentation");
1857    
1858 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1859 root 1.18 text => "Quit",
1860     tooltip => "Terminates the program",
1861     on_activate => sub {
1862 root 1.1 if ($CONN) {
1863     open_quit_dialog;
1864     } else {
1865     exit;
1866     }
1867 root 1.74 0
1868 root 1.1 },
1869     );
1870    
1871     $BUTTONBAR->show;
1872 root 1.49 $SETUP_DIALOG->show;
1873     }
1874 root 1.1
1875 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1876 root 1.1 }
1877    
1878     sub video_shutdown {
1879 root 1.104 CFPlus::OpenGL::shutdown;
1880 root 1.73
1881 root 1.1 undef $SDL_ACTIVE;
1882     }
1883    
1884     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1885     my $bgmusic;#TODO#hack#d#
1886    
1887     sub audio_channel_finished {
1888     my ($channel) = @_;
1889    
1890     #warn "channel $channel finished\n";#d#
1891     }
1892    
1893     sub audio_music_finished {
1894     return unless $CFG->{bgm_enable};
1895    
1896     # TODO: hack, do play loop and mood music
1897 root 1.104 $bgmusic = new_from_file CFPlus::MixMusic CFPlus::find_rcfile "music/$bgmusic[0]";
1898 root 1.1 $bgmusic->play (0);
1899    
1900     push @bgmusic, shift @bgmusic;
1901     }
1902    
1903     sub audio_init {
1904     if ($CFG->{audio_enable}) {
1905 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1906     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1907 root 1.1
1908     unless ($SDL_MIXER) {
1909     status "Unable to open sound device: there will be no sound";
1910     return;
1911     }
1912    
1913 root 1.104 CFPlus::Mix_AllocateChannels 8;
1914     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1915 root 1.1
1916     audio_music_finished;
1917    
1918     while (<$fh>) {
1919     next if /^\s*#/;
1920     next if /^\s*$/;
1921    
1922     my ($file, $volume, $event) = split /\s+/, $_, 3;
1923    
1924     push @SOUNDS, "$volume,$file";
1925    
1926     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1927 root 1.104 my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
1928 root 1.1 $chunk->volume ($volume * 128 / 100);
1929     $chunk
1930     };
1931     }
1932     } else {
1933     status "unable to open sound config: $!";
1934     }
1935     }
1936     }
1937    
1938     sub audio_shutdown {
1939 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1940 root 1.1 undef $SDL_MIXER;
1941     @SOUNDS = ();
1942     %AUDIO_CHUNKS = ();
1943     }
1944    
1945     my %animate_object;
1946     my $animate_timer;
1947    
1948     my $fps = 9;
1949    
1950     my %demo;#d#
1951    
1952     sub force_refresh {
1953     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1954 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1955 root 1.1
1956 root 1.104 $CFPlus::UI::ROOT->draw;
1957 root 1.1
1958     $WANT_REFRESH = 0;
1959     $CAN_REFRESH = 0;
1960     $LAST_REFRESH = $NOW;
1961    
1962 root 1.104 CFPlus::SDL_GL_SwapBuffers;
1963 root 1.1 }
1964    
1965 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1966 root 1.1 $NOW = time;
1967    
1968     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1969 root 1.104 for CFPlus::SDL_PollEvent;
1970 root 1.1
1971     if (%animate_object) {
1972     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1973     $WANT_REFRESH++;
1974     }
1975    
1976     if ($WANT_REFRESH) {
1977     force_refresh;
1978     } else {
1979     $CAN_REFRESH = 1;
1980     }
1981     });
1982    
1983     sub animation_start {
1984     my ($widget) = @_;
1985     $animate_object{$widget} = $widget;
1986     }
1987    
1988     sub animation_stop {
1989     my ($widget) = @_;
1990     delete $animate_object{$widget};
1991     }
1992    
1993     # check once/second for faces that need to be prefetched
1994     # this should, of course, only run on demand, but
1995     # SDL forces worse things on us....
1996    
1997     Event->timer (after => 1, interval => 0.25, cb => sub {
1998     $CONN->face_prefetch
1999     if $CONN;
2000     });
2001    
2002     %SDL_CB = (
2003 root 1.104 CFPlus::SDL_QUIT => sub {
2004 root 1.1 Event::unloop -1;
2005     },
2006 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2007 root 1.1 },
2008 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2009     CFPlus::UI::full_refresh;
2010 root 1.1 },
2011 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2012 root 1.1 # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
2013     },
2014 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2015     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2016 root 1.1 # alt-enter
2017 root 1.94 $FULLSCREEN_ENABLE->toggle;
2018 root 1.1 video_shutdown;
2019     video_init;
2020     } else {
2021 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2022 root 1.1 }
2023     },
2024 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2025     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2026     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2027     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2028     CFPlus::SDL_USEREVENT => sub {
2029 root 1.1 if ($_[0]{code} == 1) {
2030     audio_channel_finished $_[0]{data1};
2031     } elsif ($_[0]{code} == 0) {
2032     audio_music_finished;
2033     }
2034     },
2035     );
2036    
2037     #############################################################################
2038    
2039     $SIG{INT} = $SIG{TERM} = sub { exit };
2040    
2041     {
2042 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2043     CFPlus::UI::set_layout ($::CFG->{layout});
2044 root 1.1
2045     my %DEF_CFG = (
2046 root 1.75 sdl_mode => 0,
2047     width => 640,
2048     height => 480,
2049     fullscreen => 0,
2050     fast => 0,
2051     map_scale => 1,
2052     fow_enable => 1,
2053     fow_intensity => 0.45,
2054     fow_smooth => 0,
2055     gui_fontsize => 1,
2056     log_fontsize => 0.7,
2057     gauge_fontsize => 1,
2058     gauge_size => 0.35,
2059     stat_fontsize => 0.7,
2060     mapsize => 100,
2061 root 1.126 say_command => 'chat',
2062 root 1.75 audio_enable => 1,
2063     bgm_enable => 1,
2064     bgm_volume => 0.25,
2065     face_prefetch => 0,
2066     output_sync => 1,
2067     output_count => 1,
2068     pickup => 0,
2069 root 1.102 inv_sort => "mtime",
2070 root 1.75 default => "profile", # default profile
2071 root 1.119 show_tips => 1,
2072 root 1.1 );
2073 root 1.75
2074 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2075     $CFG->{$k} = $v unless exists $CFG->{$k};
2076     }
2077    
2078 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2079    
2080 root 1.1 sdl_init;
2081    
2082     @SDL_MODES = reverse
2083     grep $_->[0] >= 640 && $_->[1] >= 480,
2084 root 1.104 CFPlus::SDL_ListModes;
2085 root 1.1
2086 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2087 root 1.1
2088     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2089    
2090     {
2091 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2092 root 1.1 DejaVuSans.ttf
2093     DejaVuSansMono.ttf
2094     DejaVuSans-Bold.ttf
2095     DejaVuSansMono-Bold.ttf
2096     DejaVuSans-Oblique.ttf
2097     DejaVuSansMono-Oblique.ttf
2098     DejaVuSans-BoldOblique.ttf
2099     DejaVuSansMono-BoldOblique.ttf
2100     );
2101    
2102 root 1.104 CFPlus::add_font $_ for @fonts;
2103 root 1.1
2104 root 1.104 CFPlus::pango_init;
2105 root 1.1
2106 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2107     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2108 root 1.1
2109     $FONT_PROP->make_default;
2110     }
2111    
2112     # compare mono (ft) vs. rgba (cairo)
2113     # ft - 1.8s, cairo 3s, even in alpha-only mode
2114     # for my $rgba (0..1) {
2115     # my $t1 = Time::HiRes::time;
2116     # for (1..1000) {
2117 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2118 root 1.1 # $layout->set_text ("hallo" x 100);
2119     # $layout->render;
2120     # }
2121     # my $t2 = Time::HiRes::time;
2122     # warn $t2-$t1;
2123     # }
2124    
2125 root 1.122 $startup_done->();
2126    
2127 root 1.1 video_init;
2128     audio_init;
2129     }
2130    
2131 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2132    
2133 root 1.1 Event::loop;
2134 root 1.104 #CFPlus::SDL_Quit;
2135     #CFPlus::_exit 0;
2136 root 1.1
2137 root 1.104 END { CFPlus::SDL_Quit }
2138 root 1.1
2139     =head1 NAME
2140    
2141 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2142 root 1.1
2143     =head1 SYNOPSIS
2144    
2145     Just run it - no commandline arguments are supported.
2146    
2147     =head1 USAGE
2148    
2149 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2150 root 1.1 fullscreen and interactively.
2151    
2152 root 1.39 =head1 DEBUGGING
2153    
2154    
2155     CFPLUS_DEBUG - environment variable
2156    
2157     1 draw borders around widgets
2158     2 add low-level widget info to tooltips
2159     4 show fps
2160     8 suppress tooltips
2161    
2162 root 1.1 =head1 AUTHOR
2163    
2164     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2165    
2166    
2167