ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.110
Committed: Sun Aug 13 18:48:57 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
Changes since 1.109: +1 -1 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.104 $buttons->add (new CFPlus::UI::Label text => "Choose a document to display: ");
1511     $buttons->add (my $combo = new CFPlus::UI::Selector
1512 root 1.64 value => undef,
1513     options => [
1514     [intro => "Introduction"],
1515 root 1.78 [manual => "Main Manual"],
1516     [skill_help => "Skill Reference"],
1517     [command_help => "Command Reference"],
1518 root 1.64 [dmcommand_help => "DM Commands"],
1519     [COPYING => "License Terms"],
1520     ],
1521     on_changed => sub {
1522     my ($self, $pod) = @_;
1523 root 1.1
1524 root 1.64 $viewer->clear;
1525 root 1.110 $viewer->add_paragraph (CFPlus::Pod::section_paragraphs pod => $pod);
1526 root 1.64 $viewer->set_offset (0);
1527 root 1.78
1528 root 1.74 0
1529 root 1.64 },
1530     on_visibility_change => sub {
1531     my ($self, $visible) = @_;
1532     return unless $visible;
1533     return if $self->{value};
1534     $self->set_value ("intro");
1535 root 1.74 0
1536 root 1.64 },
1537     );
1538 root 1.1
1539     $win
1540     }
1541    
1542     sub sdl_init {
1543 root 1.104 CFPlus::SDL_Init
1544 root 1.1 and die "SDL::Init failed!\n";
1545     }
1546    
1547     sub video_init {
1548     sdl_init;
1549    
1550     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1551    
1552     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1553    
1554     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1555     $FULLSCREEN = $CFG->{fullscreen};
1556     $FAST = $CFG->{fast};
1557    
1558 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1559     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1560 root 1.1
1561     $SDL_ACTIVE = 1;
1562     $LAST_REFRESH = time - 0.01;
1563    
1564 root 1.104 CFPlus::OpenGL::init;
1565 root 1.1
1566     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1567    
1568 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1569 root 1.1
1570     #############################################################################
1571    
1572     if ($DEBUG_STATUS) {
1573 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1574 root 1.1 } else {
1575     # create the widgets
1576    
1577 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1578 root 1.30 padding => 0,
1579     z => 100,
1580     force_x => "max",
1581     force_y => 0;
1582 root 1.1 $DEBUG_STATUS->show;
1583 elmex 1.34
1584 root 1.104 $BIND_EDITOR = new CFPlus::BindingEditor (x => "max", y => 0);
1585 elmex 1.34
1586 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1587 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1588 root 1.1
1589 root 1.104 (new CFPlus::UI::Frame
1590 root 1.1 bg => [0, 0, 0, 0.4],
1591 root 1.30 force_x => 0,
1592     force_y => "max",
1593 root 1.1 child => $STATUSBOX,
1594     )->show;
1595    
1596 root 1.104 CFPlus::UI::FancyFrame->new (
1597 root 1.47 title => "Map",
1598 root 1.42 name => "mapmap",
1599 root 1.30 x => 0,
1600     y => $FONTSIZE + 8,
1601 root 1.1 border_bg => [1, 1, 1, 192/255],
1602     bg => [1, 1, 1, 0],
1603 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1604 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1605     ),
1606     )->show;
1607    
1608 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1609 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1610     my ($mapwidget, $preset) = @_;
1611    
1612     if ($CONSOLE) {
1613     $CONSOLE->{input}->{auto_activated} = 1;
1614 root 1.74 $CONSOLE->{input}->grab_focus;
1615 root 1.1
1616     if ($preset && $CONSOLE->{input}->get_text eq '') {
1617     $CONSOLE->{input}->set_text ($preset);
1618     }
1619     }
1620     });
1621     $MAPWIDGET->show;
1622 root 1.74 $MAPWIDGET->grab_focus;
1623 root 1.1
1624 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1625 root 1.1 expand => 1,
1626     font => $FONT_FIXED,
1627     fontsize => $::CFG->{log_fontsize},
1628 root 1.61 indent => -4,
1629 root 1.1 can_hover => 1,
1630     can_events => 1,
1631     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1632     ;
1633    
1634 root 1.104 $SETUP_DIALOG = new CFPlus::UI::FancyFrame
1635 root 1.49 title => "Setup",
1636     name => "setup_dialog",
1637     x => 'center',
1638     y => 'center',
1639 root 1.53 z => 2,
1640 root 1.49 force_w => $::WIDTH * 0.6,
1641     force_h => $::HEIGHT * 0.6,
1642 root 1.74 has_close_button => 1,
1643 root 1.49 ;
1644    
1645 elmex 1.81 $METASERVER = metaserver_dialog;
1646    
1647 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1648     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1649 root 1.49
1650     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1651     "Configure the server to play on, your username, password and other server-related options.");
1652     $SETUP_NOTEBOOK->add (Pickup => autopickup_setup,
1653 root 1.58 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1654 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1655     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1656     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1657     "Configure the use of audio, sound effects and background music.");
1658     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1659 root 1.75 "Lets you define, edit and delete key bindings."
1660     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1661 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1662 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1663 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1664     . "binding editor closes");
1665 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1666 root 1.75 "Some debuggin' options. Do not ask.");
1667 root 1.49
1668 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1669 root 1.1
1670 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1671 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1672    
1673 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1674 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1675    
1676     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
1677    
1678 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1679 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1680 root 1.1
1681 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1682 root 1.1 text => "Save Config",
1683     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1684 root 1.18 on_activate => sub {
1685 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1686     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1687 root 1.1 status "Configuration Saved";
1688 root 1.74 0
1689 root 1.1 },
1690     );
1691    
1692 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1693 root 1.1 tooltip => "View Documentation");
1694    
1695 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1696 root 1.18 text => "Quit",
1697     tooltip => "Terminates the program",
1698     on_activate => sub {
1699 root 1.1 if ($CONN) {
1700     open_quit_dialog;
1701     } else {
1702     exit;
1703     }
1704 root 1.74 0
1705 root 1.1 },
1706     );
1707    
1708     $BUTTONBAR->show;
1709 root 1.49 $SETUP_DIALOG->show;
1710     }
1711 root 1.1
1712 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1713 root 1.1 }
1714    
1715     sub video_shutdown {
1716 root 1.104 CFPlus::OpenGL::shutdown;
1717 root 1.73
1718 root 1.1 undef $SDL_ACTIVE;
1719     }
1720    
1721     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1722     my $bgmusic;#TODO#hack#d#
1723    
1724     sub audio_channel_finished {
1725     my ($channel) = @_;
1726    
1727     #warn "channel $channel finished\n";#d#
1728     }
1729    
1730     sub audio_music_finished {
1731     return unless $CFG->{bgm_enable};
1732    
1733     # TODO: hack, do play loop and mood music
1734 root 1.104 $bgmusic = new_from_file CFPlus::MixMusic CFPlus::find_rcfile "music/$bgmusic[0]";
1735 root 1.1 $bgmusic->play (0);
1736    
1737     push @bgmusic, shift @bgmusic;
1738     }
1739    
1740     sub audio_init {
1741     if ($CFG->{audio_enable}) {
1742 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1743     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1744 root 1.1
1745     unless ($SDL_MIXER) {
1746     status "Unable to open sound device: there will be no sound";
1747     return;
1748     }
1749    
1750 root 1.104 CFPlus::Mix_AllocateChannels 8;
1751     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1752 root 1.1
1753     audio_music_finished;
1754    
1755     while (<$fh>) {
1756     next if /^\s*#/;
1757     next if /^\s*$/;
1758    
1759     my ($file, $volume, $event) = split /\s+/, $_, 3;
1760    
1761     push @SOUNDS, "$volume,$file";
1762    
1763     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1764 root 1.104 my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
1765 root 1.1 $chunk->volume ($volume * 128 / 100);
1766     $chunk
1767     };
1768     }
1769     } else {
1770     status "unable to open sound config: $!";
1771     }
1772     }
1773     }
1774    
1775     sub audio_shutdown {
1776 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1777 root 1.1 undef $SDL_MIXER;
1778     @SOUNDS = ();
1779     %AUDIO_CHUNKS = ();
1780     }
1781    
1782     my %animate_object;
1783     my $animate_timer;
1784    
1785     my $fps = 9;
1786    
1787     my %demo;#d#
1788    
1789     sub force_refresh {
1790     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1791 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
1792 root 1.1
1793 root 1.104 $CFPlus::UI::ROOT->draw;
1794 root 1.1
1795     $WANT_REFRESH = 0;
1796     $CAN_REFRESH = 0;
1797     $LAST_REFRESH = $NOW;
1798    
1799 root 1.104 CFPlus::SDL_GL_SwapBuffers;
1800 root 1.1 }
1801    
1802 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
1803 root 1.1 $NOW = time;
1804    
1805     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1806 root 1.104 for CFPlus::SDL_PollEvent;
1807 root 1.1
1808     if (%animate_object) {
1809     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1810     $WANT_REFRESH++;
1811     }
1812    
1813     if ($WANT_REFRESH) {
1814     force_refresh;
1815     } else {
1816     $CAN_REFRESH = 1;
1817     }
1818     });
1819    
1820     sub animation_start {
1821     my ($widget) = @_;
1822     $animate_object{$widget} = $widget;
1823     }
1824    
1825     sub animation_stop {
1826     my ($widget) = @_;
1827     delete $animate_object{$widget};
1828     }
1829    
1830     # check once/second for faces that need to be prefetched
1831     # this should, of course, only run on demand, but
1832     # SDL forces worse things on us....
1833    
1834     Event->timer (after => 1, interval => 0.25, cb => sub {
1835     $CONN->face_prefetch
1836     if $CONN;
1837     });
1838    
1839     %SDL_CB = (
1840 root 1.104 CFPlus::SDL_QUIT => sub {
1841 root 1.1 Event::unloop -1;
1842     },
1843 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
1844 root 1.1 },
1845 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
1846     CFPlus::UI::full_refresh;
1847 root 1.1 },
1848 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
1849 root 1.1 # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1850     },
1851 root 1.104 CFPlus::SDL_KEYDOWN => sub {
1852     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
1853 root 1.1 # alt-enter
1854 root 1.94 $FULLSCREEN_ENABLE->toggle;
1855 root 1.1 video_shutdown;
1856     video_init;
1857     } else {
1858 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
1859 root 1.1 }
1860     },
1861 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
1862     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
1863     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
1864     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
1865     CFPlus::SDL_USEREVENT => sub {
1866 root 1.1 if ($_[0]{code} == 1) {
1867     audio_channel_finished $_[0]{data1};
1868     } elsif ($_[0]{code} == 0) {
1869     audio_music_finished;
1870     }
1871     },
1872     );
1873    
1874     #############################################################################
1875    
1876     $SIG{INT} = $SIG{TERM} = sub { exit };
1877    
1878     {
1879 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
1880     CFPlus::UI::set_layout ($::CFG->{layout});
1881 root 1.1
1882     my %DEF_CFG = (
1883 root 1.75 sdl_mode => 0,
1884     width => 640,
1885     height => 480,
1886     fullscreen => 0,
1887     fast => 0,
1888     map_scale => 1,
1889     fow_enable => 1,
1890     fow_intensity => 0.45,
1891     fow_smooth => 0,
1892     gui_fontsize => 1,
1893     log_fontsize => 0.7,
1894     gauge_fontsize => 1,
1895     gauge_size => 0.35,
1896     stat_fontsize => 0.7,
1897     mapsize => 100,
1898     say_command => 'say',
1899     audio_enable => 1,
1900     bgm_enable => 1,
1901     bgm_volume => 0.25,
1902     face_prefetch => 0,
1903     output_sync => 1,
1904     output_count => 1,
1905     pickup => 0,
1906 root 1.102 inv_sort => "mtime",
1907 root 1.75 default => "profile", # default profile
1908 root 1.1 );
1909 root 1.75
1910 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
1911     $CFG->{$k} = $v unless exists $CFG->{$k};
1912     }
1913    
1914 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
1915    
1916 root 1.1 sdl_init;
1917    
1918     @SDL_MODES = reverse
1919     grep $_->[0] >= 640 && $_->[1] >= 480,
1920 root 1.104 CFPlus::SDL_ListModes;
1921 root 1.1
1922 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1923 root 1.1
1924     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1925    
1926     {
1927 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
1928 root 1.1 DejaVuSans.ttf
1929     DejaVuSansMono.ttf
1930     DejaVuSans-Bold.ttf
1931     DejaVuSansMono-Bold.ttf
1932     DejaVuSans-Oblique.ttf
1933     DejaVuSansMono-Oblique.ttf
1934     DejaVuSans-BoldOblique.ttf
1935     DejaVuSansMono-BoldOblique.ttf
1936     );
1937    
1938 root 1.104 CFPlus::add_font $_ for @fonts;
1939 root 1.1
1940 root 1.104 CFPlus::pango_init;
1941 root 1.1
1942 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
1943     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
1944 root 1.1
1945     $FONT_PROP->make_default;
1946     }
1947    
1948     # compare mono (ft) vs. rgba (cairo)
1949     # ft - 1.8s, cairo 3s, even in alpha-only mode
1950     # for my $rgba (0..1) {
1951     # my $t1 = Time::HiRes::time;
1952     # for (1..1000) {
1953 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
1954 root 1.1 # $layout->set_text ("hallo" x 100);
1955     # $layout->render;
1956     # }
1957     # my $t2 = Time::HiRes::time;
1958     # warn $t2-$t1;
1959     # }
1960    
1961     video_init;
1962     audio_init;
1963     }
1964    
1965     Event::loop;
1966 root 1.104 #CFPlus::SDL_Quit;
1967     #CFPlus::_exit 0;
1968 root 1.1
1969 root 1.104 END { CFPlus::SDL_Quit }
1970 root 1.1
1971     =head1 NAME
1972    
1973 root 1.28 cfplus - A Crossfire+ and Crossfire game client
1974 root 1.1
1975     =head1 SYNOPSIS
1976    
1977     Just run it - no commandline arguments are supported.
1978    
1979     =head1 USAGE
1980    
1981 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
1982 root 1.1 fullscreen and interactively.
1983    
1984 root 1.39 =head1 DEBUGGING
1985    
1986    
1987     CFPLUS_DEBUG - environment variable
1988    
1989     1 draw borders around widgets
1990     2 add low-level widget info to tooltips
1991     4 show fps
1992     8 suppress tooltips
1993    
1994 root 1.1 =head1 AUTHOR
1995    
1996     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1997    
1998    
1999