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