ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.107
Committed: Sun Aug 13 03:20:56 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
Changes since 1.106: +3 -3 lines
Log Message:
*** empty log message ***

File Contents

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