ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.104
Committed: Sun Jul 30 13:14:15 2006 UTC (17 years, 10 months ago) by root
Branch: MAIN
Changes since 1.103: +273 -273 lines
Log Message:
the big rename

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.104 tooltip => $CFPlus::STAT_TOOLTIP{$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.104 align => +1, template => $template, tooltip => $CFPlus::STAT_TOOLTIP{$label});
727     $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.104 align => -1, text => $label, tooltip => $CFPlus::STAT_TOOLTIP{$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     sub open_quit_dialog {
1139     unless ($QUIT_DIALOG) {
1140 root 1.104 $QUIT_DIALOG = new CFPlus::UI::FancyFrame
1141 root 1.30 x => "center",
1142     y => "center",
1143 root 1.55 z => 50,
1144 root 1.30 title => "Really Quit?",
1145 elmex 1.101 on_key_down => sub {
1146     my ($dialog, $ev) = @_;
1147     $ev->{sym} == 27 and $dialog->hide;
1148     }
1149 root 1.30 ;
1150 root 1.1
1151 root 1.104 $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1152 root 1.1
1153 root 1.104 $vb->add (new CFPlus::UI::Label
1154 root 1.1 text => "You should find a savebed and apply it first!",
1155     max_w => $WIDTH * 0.25,
1156     ellipsize => 0,
1157     );
1158 root 1.104 $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1159     $hb->add (new CFPlus::UI::Button
1160 root 1.1 text => "Ok",
1161     expand => 1,
1162 root 1.74 on_activate => sub { $QUIT_DIALOG->hide; 0 },
1163 root 1.1 );
1164 root 1.104 $hb->add (new CFPlus::UI::Button
1165 root 1.1 text => "Quit anyway",
1166     expand => 1,
1167 root 1.18 on_activate => sub { exit },
1168 root 1.1 );
1169 root 1.21 }
1170 root 1.1
1171 root 1.21 $QUIT_DIALOG->show;
1172 elmex 1.101 $QUIT_DIALOG->grab_focus;
1173 root 1.1 }
1174    
1175 root 1.49 sub autopickup_setup {
1176 root 1.104 my $table = new CFPlus::UI::Table;
1177 elmex 1.44
1178 elmex 1.43 for (
1179 root 1.51 ["General", 0, 0,
1180 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1181 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1182     ["Stop before pickup" => PICKUP_STOP],
1183     ["Debug autopickup" => PICKUP_DEBUG],
1184 root 1.51 ],
1185     ["Weapons", 0, 6,
1186 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1187     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1188     ["Bows" => PICKUP_BOW],
1189     ["Arrows" => PICKUP_ARROW],
1190 root 1.51 ],
1191     ["Armour", 0, 12,
1192 root 1.58 ["Helmets" => PICKUP_HELMET],
1193     ["Shields" => PICKUP_SHIELD],
1194     ["Body Armour" => PICKUP_ARMOUR],
1195     ["Boots" => PICKUP_BOOTS],
1196     ["Gloves" => PICKUP_GLOVES],
1197     ["Cloaks" => PICKUP_CLOAK],
1198 root 1.51 ],
1199    
1200     ["Readables", 2, 2,
1201 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1202     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1203     ["Normal Books/Scrolls" => PICKUP_READABLES],
1204 root 1.51 ],
1205     ["Misc", 2, 7,
1206 root 1.58 ["Food" => PICKUP_FOOD],
1207     ["Drinks" => PICKUP_DRINK],
1208     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1209     ["Keys" => PICKUP_KEY],
1210     ["Magical Items" => PICKUP_MAGICAL],
1211     ["Potions" => PICKUP_POTION],
1212     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1213     ["Ignore cursed" => PICKUP_NOT_CURSED],
1214     ["Jewelery" => PICKUP_JEWELS],
1215 root 1.51 ],
1216 elmex 1.66 ["Weight/Value ratio", 2, 17]
1217 elmex 1.43 )
1218     {
1219 root 1.51 my ($title, $x, $y, @bits) = @$_;
1220 root 1.104 $table->add ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1221 root 1.51
1222     for (@bits) {
1223     ++$y;
1224    
1225 elmex 1.43 my $mask = $_->[1];
1226 root 1.104 $table->add ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1227     $table->add ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1228 elmex 1.83 state => $::CFG->{pickup} & $mask,
1229 elmex 1.43 on_changed => sub {
1230     my ($box, $value) = @_;
1231 root 1.63
1232 elmex 1.43 if ($value) {
1233 elmex 1.45 $::CFG->{pickup} |= $mask;
1234 elmex 1.43 } else {
1235 root 1.63 $::CFG->{pickup} &= ~$mask;
1236 elmex 1.43 }
1237 root 1.63
1238     $::CONN->send_command ("pickup $::CFG->{pickup}")
1239 elmex 1.45 if defined $::CONN;
1240 root 1.74
1241     0
1242 elmex 1.43 });
1243 root 1.86
1244     ${$_->[2]} = $checkbox if $_->[2];
1245 elmex 1.43 }
1246     }
1247    
1248 root 1.104 $table->add (2, 18, new CFPlus::UI::ValSlider
1249 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1250     template => ">= 99",
1251 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1252     on_changed => sub {
1253     my ($slider, $value) = @_;
1254    
1255 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1256 elmex 1.66 $::CFG->{pickup} |= int $value
1257     if $value;
1258     1;
1259     });
1260 elmex 1.83
1261 root 1.104 $table->add (3, 18, new CFPlus::UI::Button
1262 elmex 1.66 text => "set",
1263     on_activate => sub {
1264     $::CONN->send_command ("pickup $::CFG->{pickup}")
1265     if defined $::CONN;
1266 root 1.74 0
1267 elmex 1.66 });
1268    
1269 root 1.51 $table
1270 elmex 1.43 }
1271    
1272 root 1.102 my %SORT_ORDER = (
1273     type => undef,
1274     mtime => sub { sort {
1275     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1276     or $b->{mtime} <=> $a->{mtime}
1277     or $a->{type} <=> $b->{type}
1278     } @_ },
1279     weight => sub { sort {
1280     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1281     or $a->{type} <=> $b->{type}
1282     } @_ },
1283     );
1284    
1285 elmex 1.85 sub inventory_widget {
1286 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1287 root 1.1
1288 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1289     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1290 root 1.99
1291 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1292 root 1.99
1293     use sort 'stable';
1294    
1295 root 1.104 $hb1->add (new CFPlus::UI::Selector
1296 root 1.102 value => $::CFG->{inv_sort},
1297 root 1.99 options => [
1298 root 1.102 [type => "Type/Name"],
1299     [mtime => "Recent/Normal/Locked"],
1300     [weight => "Weight/Type"],
1301 root 1.99 ],
1302     on_changed => sub {
1303 root 1.102 $::CFG->{inv_sort} = $_[1];
1304     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1305 root 1.99 },
1306     );
1307 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1308 root 1.99 #TODO# update to weigh/maxweight
1309 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1310 root 1.99
1311 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1312     $sw1->add ($INV = new CFPlus::UI::Inventory);
1313 root 1.1
1314 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1315 elmex 1.17
1316 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1317 elmex 1.14
1318 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1319     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1320 root 1.1
1321 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1322 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1323 elmex 1.27
1324 elmex 1.85 $hb
1325 root 1.1 }
1326    
1327 root 1.86 sub toggle_player_page {
1328     my ($widget) = @_;
1329    
1330     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1331     $PL_WINDOW->hide;
1332     } else {
1333     $PL_NOTEBOOK->set_current_page ($widget);
1334     $PL_WINDOW->show;
1335     }
1336     }
1337    
1338 elmex 1.85 sub player_window {
1339 root 1.104 my $plwin = $PL_WINDOW = new CFPlus::UI::FancyFrame
1340 elmex 1.85 x => "center",
1341     y => "center",
1342     force_w => $WIDTH * 9/10,
1343     force_h => $HEIGHT * 9/10,
1344     title => "Player",
1345 elmex 1.90 name => "playerbook",
1346 elmex 1.85 has_close_button => 1
1347     ;
1348    
1349     my $ntb =
1350     $PL_NOTEBOOK =
1351 root 1.104 new CFPlus::UI::Notebook expand => 1, debug => 1;
1352 root 1.86
1353 elmex 1.85 $ntb->add (
1354 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1355 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1356     );
1357     $ntb->add (
1358 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1359 elmex 1.92 "Shows all your Skills."
1360 elmex 1.85 );
1361 elmex 1.97
1362 root 1.104 my $spellsw = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1363     $spellsw->add ($SPELL_PAGE = new CFPlus::UI::SpellList);
1364 elmex 1.85 $ntb->add (
1365 elmex 1.97 "Spellbook (F4)" => $spellsw,
1366 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1367 elmex 1.85 );
1368     $ntb->add (
1369 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1370 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1371     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1372 elmex 1.85 );
1373    
1374 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1375 root 1.86
1376 elmex 1.85 $plwin->add ($ntb);
1377     $plwin
1378 elmex 1.38 }
1379    
1380 elmex 1.77 sub update_bindings {
1381     $BIND_UPD_CB->() if $BIND_UPD_CB;
1382     }
1383    
1384 root 1.49 sub keyboard_setup {
1385 root 1.104 my $binding_list = new CFPlus::UI::VBox;
1386 elmex 1.24
1387 elmex 1.34 my $refresh;
1388 elmex 1.77 $refresh = $BIND_UPD_CB = sub {
1389 elmex 1.24 $binding_list->clear ();
1390    
1391 root 1.75 for my $mod (keys %{$::CFG->{profile}{default}{bindings}}) {
1392     for my $sym (keys %{$::CFG->{profile}{default}{bindings}{$mod}}) {
1393     my $cmds = $::CFG->{profile}{default}{bindings}{$mod}{$sym};
1394 elmex 1.24 next unless ref $cmds eq 'ARRAY' and @$cmds > 0;
1395    
1396     my $lbl = join "; ", @$cmds;
1397 root 1.104 my $nam = CFPlus::BindingEditor::keycombo_to_name ($mod, $sym);
1398     $binding_list->add (my $hb = new CFPlus::UI::HBox);
1399     $hb->add (new CFPlus::UI::Button
1400 elmex 1.25 text => "delete",
1401 elmex 1.34 tooltip => "Deletes the binding",
1402 elmex 1.24 on_activate => sub {
1403     $binding_list->remove ($hb);
1404 root 1.75 delete $::CFG->{profile}{default}{bindings}{$mod}{$sym};
1405 root 1.74 0
1406 elmex 1.24 });
1407 elmex 1.34
1408 root 1.104 $hb->add (new CFPlus::UI::Button
1409 elmex 1.34 text => "edit",
1410     tooltip => "Edits the binding",
1411     on_activate => sub {
1412     $::BIND_EDITOR->set_binding (
1413 root 1.75 $mod, $sym, $::CFG->{profile}{default}{bindings}{$mod}{$sym},
1414 elmex 1.34 sub {
1415     my ($nmod, $nsym, $ncmds) = @_;
1416 elmex 1.77 $::BIND_EDITOR->cfg_unbind ($mod, $sym);
1417     $::BIND_EDITOR->cfg_bind ($nmod, $nsym, $ncmds);
1418 elmex 1.34 $refresh->();
1419 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1420     $SETUP_DIALOG->show;
1421 elmex 1.34 },
1422     sub {
1423 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1424     $SETUP_DIALOG->show;
1425 elmex 1.34 });
1426     $::BIND_EDITOR->show;
1427 root 1.49 $SETUP_DIALOG->hide;
1428 root 1.74 0
1429 elmex 1.34 });
1430    
1431 root 1.104 $hb->add (new CFPlus::UI::Label text => "(Key: $nam)");
1432     $hb->add (new CFPlus::UI::Label text => $lbl, expand => 1);
1433 elmex 1.24 }
1434     }
1435     };
1436    
1437 root 1.104 my $vb = new CFPlus::UI::VBox;
1438     $vb->add (my $hb = new CFPlus::UI::HBox);
1439     $hb->add (new CFPlus::UI::Label text => "only shift-up stops fire");
1440     $hb->add (new CFPlus::UI::CheckBox
1441 elmex 1.71 expand => 1,
1442     state => $CFG->{shift_fire_stop},
1443     tooltip => "If this checkbox is enabled you will stop fire only if you stop pressing shift",
1444     on_changed => sub {
1445     my ($cbox, $value) = @_;
1446     $CFG->{shift_fire_stop} = $value;
1447 root 1.74 0
1448 elmex 1.71 });
1449    
1450 elmex 1.35 $vb->add ($binding_list);
1451 root 1.104 $vb->add (my $hb = new CFPlus::UI::HBox);
1452 root 1.49
1453 root 1.104 $hb->add (new CFPlus::UI::Button
1454 elmex 1.34 text => "record new",
1455 elmex 1.35 expand => 1,
1456 elmex 1.34 tooltip => "This button opens the binding editor with an empty binding.",
1457     on_activate => sub {
1458     $::BIND_EDITOR->set_binding (undef, undef, [],
1459     sub {
1460     my ($mod, $sym, $cmds) = @_;
1461 elmex 1.77 $::BIND_EDITOR->cfg_bind ($mod, $sym, $cmds);
1462 elmex 1.34 $refresh->();
1463 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1464     $SETUP_DIALOG->show;
1465 elmex 1.34 },
1466     sub {
1467 root 1.49 $SETUP_NOTEBOOK->set_current_page ($SETUP_KEYBOARD);
1468     $SETUP_DIALOG->show;
1469 root 1.53 },
1470     );
1471 root 1.49 $SETUP_DIALOG->hide;
1472 elmex 1.34 $::BIND_EDITOR->show;
1473 root 1.74 0
1474 elmex 1.34 },
1475     );
1476 root 1.49
1477 root 1.104 $hb->add (new CFPlus::UI::Button
1478 elmex 1.35 text => "close",
1479     tooltip => "Closes the binding window",
1480     expand => 1,
1481     on_activate => sub {
1482 root 1.49 $SETUP_DIALOG->hide;
1483 root 1.74 0
1484 elmex 1.35 }
1485     );
1486    
1487 elmex 1.24 $refresh->();
1488 root 1.49
1489     $vb
1490 elmex 1.24 }
1491    
1492 root 1.64 sub help_window {
1493 root 1.104 my $win = new CFPlus::UI::FancyFrame
1494 root 1.41 x => 'center',
1495     y => 'center',
1496 root 1.55 z => 2,
1497 root 1.41 name => 'doc_browser',
1498     force_w => int $WIDTH * 7/8,
1499     force_h => int $HEIGHT * 7/8,
1500 root 1.87 title => "Help Browser",
1501     has_close_button => 1;
1502 root 1.1
1503 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1504 root 1.1
1505 root 1.104 $vbox->add (my $buttons = new CFPlus::UI::HBox);
1506     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1507 root 1.64 expand => 1, fontsize => 0.8, padding_x => 4);
1508 root 1.1
1509 root 1.104 $buttons->add (new CFPlus::UI::Label text => "Choose a document to display: ");
1510     $buttons->add (my $combo = new CFPlus::UI::Selector
1511 root 1.64 value => undef,
1512     options => [
1513     [intro => "Introduction"],
1514 root 1.78 [manual => "Main Manual"],
1515     [skill_help => "Skill Reference"],
1516     [command_help => "Command Reference"],
1517 root 1.64 [dmcommand_help => "DM Commands"],
1518     [COPYING => "License Terms"],
1519     ],
1520     on_changed => sub {
1521     my ($self, $pod) = @_;
1522 root 1.1
1523 root 1.64 $viewer->clear;
1524 root 1.104 $viewer->add_paragraph (@{ CFPlus::Pod::pod_paragraphs $pod });
1525 root 1.64 $viewer->set_offset (0);
1526 root 1.78
1527 root 1.74 0
1528 root 1.64 },
1529     on_visibility_change => sub {
1530     my ($self, $visible) = @_;
1531     return unless $visible;
1532     return if $self->{value};
1533     $self->set_value ("intro");
1534 root 1.74 0
1535 root 1.64 },
1536     );
1537 root 1.1
1538     $win
1539     }
1540    
1541     sub sdl_init {
1542 root 1.104 CFPlus::SDL_Init
1543 root 1.1 and die "SDL::Init failed!\n";
1544     }
1545    
1546     sub video_init {
1547     sdl_init;
1548    
1549     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1550    
1551     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1552    
1553     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1554     $FULLSCREEN = $CFG->{fullscreen};
1555     $FAST = $CFG->{fast};
1556    
1557 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1558     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1559 root 1.1
1560     $SDL_ACTIVE = 1;
1561     $LAST_REFRESH = time - 0.01;
1562    
1563 root 1.104 CFPlus::OpenGL::init;
1564 root 1.1
1565     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1566    
1567 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1568 root 1.1
1569     #############################################################################
1570    
1571     if ($DEBUG_STATUS) {
1572 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1573 root 1.1 } else {
1574     # create the widgets
1575    
1576 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1577 root 1.30 padding => 0,
1578     z => 100,
1579     force_x => "max",
1580     force_y => 0;
1581 root 1.1 $DEBUG_STATUS->show;
1582 elmex 1.34
1583 root 1.104 $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0);
1584 elmex 1.34
1585 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1586 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1587 root 1.1
1588 root 1.104 (new CFPlus::UI::Frame
1589 root 1.1 bg => [0, 0, 0, 0.4],
1590 root 1.30 force_x => 0,
1591     force_y => "max",
1592 root 1.1 child => $STATUSBOX,
1593     )->show;
1594    
1595 root 1.104 CFPlus::UI::FancyFrame->new (
1596 root 1.47 title => "Map",
1597 root 1.42 name => "mapmap",
1598 root 1.30 x => 0,
1599     y => $FONTSIZE + 8,
1600 root 1.1 border_bg => [1, 1, 1, 192/255],
1601     bg => [1, 1, 1, 0],
1602 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1603 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1604     ),
1605     )->show;
1606    
1607 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1608 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1609     my ($mapwidget, $preset) = @_;
1610    
1611     if ($CONSOLE) {
1612     $CONSOLE->{input}->{auto_activated} = 1;
1613 root 1.74 $CONSOLE->{input}->grab_focus;
1614 root 1.1
1615     if ($preset && $CONSOLE->{input}->get_text eq '') {
1616     $CONSOLE->{input}->set_text ($preset);
1617     }
1618     }
1619     });
1620     $MAPWIDGET->show;
1621 root 1.74 $MAPWIDGET->grab_focus;
1622 root 1.1
1623 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1624 root 1.1 expand => 1,
1625     font => $FONT_FIXED,
1626     fontsize => $::CFG->{log_fontsize},
1627 root 1.61 indent => -4,
1628 root 1.1 can_hover => 1,
1629     can_events => 1,
1630     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1631     ;
1632    
1633 root 1.104 $SETUP_DIALOG = new CFPlus::UI::FancyFrame
1634 root 1.49 title => "Setup",
1635     name => "setup_dialog",
1636     x => 'center',
1637     y => 'center',
1638 root 1.53 z => 2,
1639 root 1.49 force_w => $::WIDTH * 0.6,
1640     force_h => $::HEIGHT * 0.6,
1641 root 1.74 has_close_button => 1,
1642 root 1.49 ;
1643    
1644 elmex 1.81 $METASERVER = metaserver_dialog;
1645    
1646 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1647     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1648 root 1.49
1649     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1650     "Configure the server to play on, your username, password and other server-related options.");
1651     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1652 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1653 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1654     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1655     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1656     "Configure the use of audio, sound effects and background music.");
1657     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1658 root 1.75 "Lets you define, edit and delete key bindings."
1659     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1660 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1661 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1662 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1663     . "binding editor closes");
1664 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1665 root 1.75 "Some debuggin' options. Do not ask.");
1666 root 1.49
1667 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1668 root 1.1
1669 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1670 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1671    
1672 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1673 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1674    
1675     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
1676    
1677 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1678 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1679 root 1.1
1680 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1681 root 1.1 text => "Save Config",
1682     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1683 root 1.18 on_activate => sub {
1684 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1685     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1686 root 1.1 status "Configuration Saved";
1687 root 1.74 0
1688 root 1.1 },
1689     );
1690    
1691 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1692 root 1.1 tooltip => "View Documentation");
1693    
1694 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1695 root 1.18 text => "Quit",
1696     tooltip => "Terminates the program",
1697     on_activate => sub {
1698 root 1.1 if ($CONN) {
1699     open_quit_dialog;
1700     } else {
1701     exit;
1702     }
1703 root 1.74 0
1704 root 1.1 },
1705     );
1706    
1707     $BUTTONBAR->show;
1708 root 1.49 $SETUP_DIALOG->show;
1709     }
1710 root 1.1
1711 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1712 root 1.1 }
1713    
1714     sub video_shutdown {
1715 root 1.104 CFPlus::OpenGL::shutdown;
1716 root 1.73
1717 root 1.1 undef $SDL_ACTIVE;
1718     }
1719    
1720     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1721     my $bgmusic;#TODO#hack#d#
1722    
1723     sub audio_channel_finished {
1724     my ($channel) = @_;
1725    
1726     #warn "channel $channel finished\n";#d#
1727     }
1728    
1729     sub audio_music_finished {
1730     return unless $CFG->{bgm_enable};
1731    
1732     # TODO: hack, do play loop and mood music
1733 root 1.104 $bgmusic = new_from_file CFPlus::MixMusic CFPlus::find_rcfile "music/$bgmusic[0]";
1734 root 1.1 $bgmusic->play (0);
1735    
1736     push @bgmusic, shift @bgmusic;
1737     }
1738    
1739     sub audio_init {
1740     if ($CFG->{audio_enable}) {
1741 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1742     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1743 root 1.1
1744     unless ($SDL_MIXER) {
1745     status "Unable to open sound device: there will be no sound";
1746     return;
1747     }
1748    
1749 root 1.104 CFPlus::Mix_AllocateChannels 8;
1750     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1751 root 1.1
1752     audio_music_finished;
1753    
1754     while (<$fh>) {
1755     next if /^\s*#/;
1756     next if /^\s*$/;
1757    
1758     my ($file, $volume, $event) = split /\s+/, $_, 3;
1759    
1760     push @SOUNDS, "$volume,$file";
1761    
1762     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1763 root 1.104 my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
1764 root 1.1 $chunk->volume ($volume * 128 / 100);
1765     $chunk
1766     };
1767     }
1768     } else {
1769     status "unable to open sound config: $!";
1770     }
1771     }
1772     }
1773    
1774     sub audio_shutdown {
1775 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1776 root 1.1 undef $SDL_MIXER;
1777     @SOUNDS = ();
1778     %AUDIO_CHUNKS = ();
1779     }
1780    
1781     my %animate_object;
1782     my $animate_timer;
1783    
1784     my $fps = 9;
1785    
1786     my %demo;#d#
1787    
1788     sub force_refresh {
1789     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1790 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1791 root 1.1
1792 root 1.104 $CFPlus::UI::ROOT->draw;
1793 root 1.1
1794     $WANT_REFRESH = 0;
1795     $CAN_REFRESH = 0;
1796     $LAST_REFRESH = $NOW;
1797    
1798 root 1.104 CFPlus::SDL_GL_SwapBuffers;
1799 root 1.1 }
1800    
1801 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1802 root 1.1 $NOW = time;
1803    
1804     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1805 root 1.104 for CFPlus::SDL_PollEvent;
1806 root 1.1
1807     if (%animate_object) {
1808     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1809     $WANT_REFRESH++;
1810     }
1811    
1812     if ($WANT_REFRESH) {
1813     force_refresh;
1814     } else {
1815     $CAN_REFRESH = 1;
1816     }
1817     });
1818    
1819     sub animation_start {
1820     my ($widget) = @_;
1821     $animate_object{$widget} = $widget;
1822     }
1823    
1824     sub animation_stop {
1825     my ($widget) = @_;
1826     delete $animate_object{$widget};
1827     }
1828    
1829     # check once/second for faces that need to be prefetched
1830     # this should, of course, only run on demand, but
1831     # SDL forces worse things on us....
1832    
1833     Event->timer (after => 1, interval => 0.25, cb => sub {
1834     $CONN->face_prefetch
1835     if $CONN;
1836     });
1837    
1838     %SDL_CB = (
1839 root 1.104 CFPlus::SDL_QUIT => sub {
1840 root 1.1 Event::unloop -1;
1841     },
1842 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
1843 root 1.1 },
1844 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
1845     CFPlus::UI::full_refresh;
1846 root 1.1 },
1847 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
1848 root 1.1 # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1849     },
1850 root 1.104 CFPlus::SDL_KEYDOWN => sub {
1851     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
1852 root 1.1 # alt-enter
1853 root 1.94 $FULLSCREEN_ENABLE->toggle;
1854 root 1.1 video_shutdown;
1855     video_init;
1856     } else {
1857 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
1858 root 1.1 }
1859     },
1860 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
1861     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
1862     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
1863     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
1864     CFPlus::SDL_USEREVENT => sub {
1865 root 1.1 if ($_[0]{code} == 1) {
1866     audio_channel_finished $_[0]{data1};
1867     } elsif ($_[0]{code} == 0) {
1868     audio_music_finished;
1869     }
1870     },
1871     );
1872    
1873     #############################################################################
1874    
1875     $SIG{INT} = $SIG{TERM} = sub { exit };
1876    
1877     {
1878 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
1879     CFPlus::UI::set_layout ($::CFG->{layout});
1880 root 1.1
1881     my %DEF_CFG = (
1882 root 1.75 sdl_mode => 0,
1883     width => 640,
1884     height => 480,
1885     fullscreen => 0,
1886     fast => 0,
1887     map_scale => 1,
1888     fow_enable => 1,
1889     fow_intensity => 0.45,
1890     fow_smooth => 0,
1891     gui_fontsize => 1,
1892     log_fontsize => 0.7,
1893     gauge_fontsize => 1,
1894     gauge_size => 0.35,
1895     stat_fontsize => 0.7,
1896     mapsize => 100,
1897     say_command => 'say',
1898     audio_enable => 1,
1899     bgm_enable => 1,
1900     bgm_volume => 0.25,
1901     face_prefetch => 0,
1902     output_sync => 1,
1903     output_count => 1,
1904     pickup => 0,
1905 root 1.102 inv_sort => "mtime",
1906 root 1.75 default => "profile", # default profile
1907 root 1.1 );
1908 root 1.75
1909 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
1910     $CFG->{$k} = $v unless exists $CFG->{$k};
1911     }
1912    
1913 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
1914    
1915 root 1.1 sdl_init;
1916    
1917     @SDL_MODES = reverse
1918     grep $_->[0] >= 640 && $_->[1] >= 480,
1919 root 1.104 CFPlus::SDL_ListModes;
1920 root 1.1
1921 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1922 root 1.1
1923     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1924    
1925     {
1926 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
1927 root 1.1 DejaVuSans.ttf
1928     DejaVuSansMono.ttf
1929     DejaVuSans-Bold.ttf
1930     DejaVuSansMono-Bold.ttf
1931     DejaVuSans-Oblique.ttf
1932     DejaVuSansMono-Oblique.ttf
1933     DejaVuSans-BoldOblique.ttf
1934     DejaVuSansMono-BoldOblique.ttf
1935     );
1936    
1937 root 1.104 CFPlus::add_font $_ for @fonts;
1938 root 1.1
1939 root 1.104 CFPlus::pango_init;
1940 root 1.1
1941 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
1942     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
1943 root 1.1
1944     $FONT_PROP->make_default;
1945     }
1946    
1947     # compare mono (ft) vs. rgba (cairo)
1948     # ft - 1.8s, cairo 3s, even in alpha-only mode
1949     # for my $rgba (0..1) {
1950     # my $t1 = Time::HiRes::time;
1951     # for (1..1000) {
1952 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
1953 root 1.1 # $layout->set_text ("hallo" x 100);
1954     # $layout->render;
1955     # }
1956     # my $t2 = Time::HiRes::time;
1957     # warn $t2-$t1;
1958     # }
1959    
1960     video_init;
1961     audio_init;
1962     }
1963    
1964     Event::loop;
1965 root 1.104 #CFPlus::SDL_Quit;
1966     #CFPlus::_exit 0;
1967 root 1.1
1968 root 1.104 END { CFPlus::SDL_Quit }
1969 root 1.1
1970     =head1 NAME
1971    
1972 root 1.28 cfplus - A Crossfire+ and Crossfire game client
1973 root 1.1
1974     =head1 SYNOPSIS
1975    
1976     Just run it - no commandline arguments are supported.
1977    
1978     =head1 USAGE
1979    
1980 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
1981 root 1.1 fullscreen and interactively.
1982    
1983 root 1.39 =head1 DEBUGGING
1984    
1985    
1986     CFPLUS_DEBUG - environment variable
1987    
1988     1 draw borders around widgets
1989     2 add low-level widget info to tooltips
1990     4 show fps
1991     8 suppress tooltips
1992    
1993 root 1.1 =head1 AUTHOR
1994    
1995     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1996    
1997    
1998