ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.171
Committed: Sun Jul 15 21:42:02 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.170: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.122 my $startup_done = sub { };
4 root 1.159 our $PANGO = "1.5.0";
5 root 1.122
6     # do splash-screen thingy on win32
7     BEGIN {
8 root 1.123 if (%PAR::LibCache && $^O eq "MSWin32") {
9 root 1.122 while (my ($filename, $zip) = each %PAR::LibCache) {
10     $zip->extractMember ("SPLASH.bmp", "$ENV{PAR_TEMP}/SPLASH.bmp");
11     }
12    
13     require Win32::GUI::SplashScreen;
14    
15     Win32::GUI::SplashScreen::Show (
16     -file => "$ENV{PAR_TEMP}/SPLASH.bmp",
17     );
18    
19     $startup_done = sub {
20     Win32::GUI::SplashScreen::Done (1);
21     };
22     }
23     }
24    
25 root 1.1 use strict;
26     use utf8;
27    
28 root 1.132 use Carp 'verbose';
29    
30 root 1.1 # do things only needed for single-binary version (par)
31     BEGIN {
32     if (%PAR::LibCache) {
33     @INC = grep ref, @INC; # weed out all paths except pars loader refs
34    
35 root 1.154 my $tmp = $ENV{PAR_TEMP};
36    
37 root 1.1 while (my ($filename, $zip) = each %PAR::LibCache) {
38     for ($zip->memberNames) {
39 root 1.122 next unless /^root\/(.*)/;
40 root 1.154 $zip->extractMember ($_, "$tmp/$1")
41     unless -e "$tmp/$1";
42 root 1.1 }
43     }
44    
45 root 1.154 if ($^O eq "MSWin32") {
46     # relocatable
47     } else {
48     # unix, need to patch pango rc file
49 root 1.159 open my $fh, "<:perlio", "$tmp/usr/lib/pango/$PANGO/module-files.d/libpango1.0-0.modules"
50     or die "$tmp/usr/lib/$PANGO/module-files.d/libpango1.0-0.modules: $!";
51 root 1.154 local $/;
52     my $rc = <$fh>;
53     $rc =~ s/^\//$tmp\//gm; # replace abs paths by relative ones
54    
55     mkdir "$tmp/pango-modules";
56     open my $fh, ">:perlio", "$tmp/pango-modules/pango.modules"
57     or die "$tmp/pango-modules/pango.modules: $!";
58     print $fh $rc;
59    
60     $ENV{PANGO_RC_FILE} = "$tmp/pango.rc";
61     open my $fh, ">:perlio", $ENV{PANGO_RC_FILE}
62     or die "$ENV{PANGO_RC_FILE}: $!";
63     print $fh "[Pango]\nModuleFiles = $tmp/pango-modules\n";
64     }
65 root 1.1
66 root 1.154 unshift @INC, $tmp;
67 root 1.1 }
68     }
69    
70     # need to do it again because that pile of garbage called PAR nukes it before main
71     unshift @INC, $ENV{PAR_TEMP}
72     if %PAR::LibCache;
73    
74     use Time::HiRes 'time';
75     use Event;
76    
77     use Crossfire;
78 root 1.12 use Crossfire::Protocol::Constants;
79 root 1.1
80     use Compress::LZF;
81    
82 root 1.104 use CFPlus;
83     use CFPlus::OpenGL ();
84     use CFPlus::Protocol;
85 root 1.147 use CFPlus::DB;
86 root 1.104 use CFPlus::UI;
87 root 1.137 use CFPlus::UI::Inventory;
88     use CFPlus::UI::SpellList;
89 root 1.104 use CFPlus::Pod;
90     use CFPlus::MapWidget;
91 root 1.137 use CFPlus::Macro;
92 root 1.1
93 root 1.59 $SIG{QUIT} = sub { Carp::cluck "QUIT" };
94 root 1.91 $SIG{PIPE} = 'IGNORE';
95 root 1.59
96 root 1.139 $Event::Eval = 1;
97 root 1.1 $Event::DIED = sub {
98 root 1.132 CFPlus::fatal Carp::longmess $_[1]
99 root 1.103 };
100 root 1.1
101     my $MAX_FPS = 60;
102     my $MIN_FPS = 5; # unused as of yet
103    
104 root 1.125 our $META_SERVER = "http://metaserver.schmorp.de/current.json";
105 root 1.1
106     our $LAST_REFRESH;
107     our $NOW;
108    
109     our $CFG;
110     our $CONN;
111 root 1.120 our $PROFILE; # current profile
112 root 1.1 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
113    
114     our $WANT_REFRESH;
115     our $CAN_REFRESH;
116    
117     our @SDL_MODES;
118     our $WIDTH;
119     our $HEIGHT;
120     our $FULLSCREEN;
121     our $FONTSIZE;
122    
123     our $FONT_PROP;
124     our $FONT_FIXED;
125    
126     our $MAP;
127     our $MAPMAP;
128     our $MAPWIDGET;
129     our $BUTTONBAR;
130     our $LOGVIEW;
131     our $CONSOLE;
132     our $METASERVER;
133     our $LOGIN_BUTTON;
134     our $QUIT_DIALOG;
135 root 1.40 our $HOST_ENTRY;
136 root 1.94 our $FULLSCREEN_ENABLE;
137 root 1.86 our $PICKUP_ENABLE;
138 root 1.67 our $SERVER_INFO;
139 root 1.49
140     our $SETUP_DIALOG;
141     our $SETUP_NOTEBOOK;
142     our $SETUP_SERVER;
143     our $SETUP_KEYBOARD;
144 root 1.1
145 root 1.86 our $PL_NOTEBOOK;
146     our $PL_WINDOW;
147    
148     our $INVENTORY_PAGE;
149     our $STATS_PAGE;
150 root 1.95 our $SKILL_PAGE;
151 root 1.86 our $SPELL_PAGE;
152 root 1.131 our $SPELL_LIST;
153 root 1.86
154     our $HELP_WINDOW;
155 root 1.60 our $MESSAGE_WINDOW;
156 root 1.1 our $FLOORBOX;
157     our $GAUGES;
158     our $STATWIDS;
159    
160     our $SDL_ACTIVE;
161     our %SDL_CB;
162    
163     our $SDL_MIXER;
164 root 1.163 our $MUSIC_DEFAULT = "in_a_heartbeat.ogg";
165     our @MUSIC_WANT;
166 root 1.168 our $MUSIC_START;
167 root 1.163 our $MUSIC_PLAYING;
168     our $MUSIC_PLAYER;
169 root 1.168 our $MUSIC_RESUME = 30; # resume music when players less than these many seconds before
170 root 1.1 our @SOUNDS; # event => file mapping
171     our %AUDIO_CHUNKS; # audio files
172    
173     our $ALT_ENTER_MESSAGE;
174     our $STATUSBOX;
175     our $DEBUG_STATUS;
176    
177     our $INV;
178     our $INVR;
179 elmex 1.27 our $INV_RIGHT_HB;
180 root 1.1
181 elmex 1.43 our $PICKUP_CFG;
182 elmex 1.38
183 elmex 1.134 our $IN_BUILD_MODE;
184     our $BUILD_BUTTON;
185    
186 root 1.1 sub status {
187 root 1.104 $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
188 root 1.1 }
189    
190     sub debug {
191     $DEBUG_STATUS->set_text ($_[0]);
192     }
193    
194 root 1.122 sub message {
195     my ($para) = @_;
196    
197     my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
198    
199     $para->{markup} = "<span foreground='#ffffff'>$time</span> $para->{markup}";
200    
201     $LOGVIEW->add_paragraph ($para);
202     $LOGVIEW->scroll_to_bottom;
203     }
204    
205 root 1.60 sub destroy_query_dialog {
206     (delete $_[0]{query_dialog})->destroy
207     if $_[0]{query_dialog};
208     }
209    
210 elmex 1.141 # FIXME: a very ugly hack to wait for stat update look below! #d#
211     our $QUERY_TIMER; #d#
212    
213 root 1.60 # server query dialog
214     sub server_query {
215     my ($conn, $flags, $prompt) = @_;
216    
217 elmex 1.141 # FIXME: a very ugly hack to wait for stat update #d#
218     if ($prompt =~ /roll new stats/ and not $conn->{stat_change_with}) {
219     unless ($QUERY_TIMER) {
220     $QUERY_TIMER =
221     Event->timer (
222     after => 1,
223     cb => sub {
224     server_query ($conn, $flags, $prompt, 1);
225     $QUERY_TIMER = undef
226     }
227     );
228     return;
229     }
230     }
231    
232 root 1.114 $conn->{query_dialog} = my $dialog = new CFPlus::UI::Toplevel
233 root 1.60 x => "center",
234     y => "center",
235     title => "Server Query",
236 root 1.104 child => my $vbox = new CFPlus::UI::VBox,
237 root 1.60 ;
238    
239 root 1.104 my @dialog = my $label = new CFPlus::UI::Label
240 root 1.152 max_w => $::WIDTH * 0.8,
241 root 1.60 ellipsise => 0,
242     text => $prompt;
243    
244     if ($flags & CS_QUERY_YESNO) {
245 root 1.104 push @dialog, my $hbox = new CFPlus::UI::HBox;
246 root 1.60
247 root 1.104 $hbox->add (new CFPlus::UI::Button
248 root 1.60 text => "No",
249     on_activate => sub {
250     $conn->send ("reply n");
251     $dialog->destroy;
252 root 1.74 0
253 root 1.60 }
254     );
255 root 1.104 $hbox->add (new CFPlus::UI::Button
256 root 1.60 text => "Yes",
257     on_activate => sub {
258     $conn->send ("reply y");
259     destroy_query_dialog $conn;
260 root 1.74 0
261 root 1.60 },
262     );
263    
264 root 1.74 $dialog->grab_focus;
265 root 1.60
266     } elsif ($flags & CS_QUERY_SINGLECHAR) {
267     if ($prompt =~ /Now choose a character|Press any key for the next race/i) {
268 root 1.152 $dialog->{tooltip} = "#charcreation_focus";
269 root 1.60
270 root 1.104 unshift @dialog, new CFPlus::UI::Label
271 root 1.152 max_w => $::WIDTH * 0.8,
272 root 1.60 ellipsise => 0,
273 root 1.98 markup => "\nOr use your keyboard and the text entry below:\n";
274 root 1.60
275 root 1.104 unshift @dialog, my $table = new CFPlus::UI::Table;
276 root 1.60
277 root 1.104 $table->add (0, 0, new CFPlus::UI::Button
278 root 1.60 text => "Next Race",
279     on_activate => sub {
280     $conn->send ("reply n");
281     destroy_query_dialog $conn;
282 root 1.74 0
283 root 1.60 },
284     );
285 root 1.104 $table->add (2, 0, new CFPlus::UI::Button
286 root 1.60 text => "Accept",
287     on_activate => sub {
288     $conn->send ("reply d");
289     destroy_query_dialog $conn;
290 root 1.74 0
291 root 1.60 },
292     );
293    
294 root 1.152 if ($conn->{chargen_race_description}) {
295     unshift @dialog, new CFPlus::UI::Label
296     max_w => $::WIDTH * 0.8,
297     ellipsise => 0,
298     markup => "<span foreground='#ccccff'>$conn->{chargen_race_description}</span>",
299     ;
300     }
301    
302 root 1.153 unshift @dialog, new CFPlus::UI::Face
303     face => $conn->{player}{face},
304     bg => [.2, .2, .2, 1],
305     min_w => 64,
306     min_h => 64,
307     ;
308 root 1.152
309     if ($conn->{chargen_race_title}) {
310     unshift @dialog, new CFPlus::UI::Label
311     allign => 1,
312     ellipsise => 0,
313     markup => "<span foreground='#ccccff' size='large'>Race: $conn->{chargen_race_title}</span>",
314     ;
315     }
316    
317 root 1.104 unshift @dialog, new CFPlus::UI::Label
318 root 1.60 max_w => $::WIDTH * 0.4,
319     ellipsise => 0,
320 root 1.109 markup => (CFPlus::Pod::section_label ui => "chargen_race"),
321 root 1.60 ;
322    
323     } elsif ($prompt =~ /roll new stats/) {
324     if (my $stat = delete $conn->{stat_change_with}) {
325     $conn->send ("reply $stat");
326     destroy_query_dialog $conn;
327     return;
328     }
329    
330 root 1.86 $STATS_PAGE->show;
331 root 1.60 $MESSAGE_WINDOW->hide;
332    
333 root 1.104 unshift @dialog, new CFPlus::UI::Label
334 root 1.60 max_w => $::WIDTH * 0.4,
335     ellipsise => 0,
336 root 1.98 markup => "\nOr use your keyboard and the text entry below:\n";
337 root 1.60
338 root 1.104 unshift @dialog, my $table = new CFPlus::UI::Table;
339 root 1.60
340     # left: re-roll
341 root 1.104 $table->add (0, 0, new CFPlus::UI::Button
342 root 1.60 text => "Roll Again",
343     on_activate => sub {
344     $conn->send ("reply y");
345     destroy_query_dialog $conn;
346 root 1.74 0
347 root 1.60 },
348     );
349    
350     # center: swap stats
351 root 1.104 my ($sw1, $sw2) = map +(new CFPlus::UI::Selector
352 root 1.98 expand => 1,
353 root 1.60 value => $_,
354     options => [
355 root 1.64 [1 => "Str", "Strength ($conn->{stat}{+CS_STAT_STR})"],
356     [2 => "Dex", "Dexterity ($conn->{stat}{+CS_STAT_DEX})"],
357     [3 => "Con", "Constitution ($conn->{stat}{+CS_STAT_CON})"],
358     [4 => "Int", "Intelligence ($conn->{stat}{+CS_STAT_INT})"],
359     [5 => "Wis", "Wisdom ($conn->{stat}{+CS_STAT_WIS})"],
360     [6 => "Pow", "Power ($conn->{stat}{+CS_STAT_POW})"],
361     [7 => "Cha", "Charisma ($conn->{stat}{+CS_STAT_CHA})"],
362 root 1.60 ],
363     ), 1 .. 2;
364    
365 root 1.104 $table->add (2, 0, new CFPlus::UI::Button
366 root 1.60 text => "Swap Stats",
367     on_activate => sub {
368     $conn->{stat_change_with} = $sw2->{value};
369     $conn->send ("reply $sw1->{value}");
370     destroy_query_dialog $conn;
371 root 1.74 0
372 root 1.60 },
373     );
374 root 1.104 $table->add (2, 1, new CFPlus::UI::HBox children => [$sw1, $sw2]);
375 root 1.60
376     # right: accept
377 root 1.104 $table->add (4, 0, new CFPlus::UI::Button
378 root 1.60 text => "Accept",
379     on_activate => sub {
380     $conn->send ("reply n");
381 root 1.86 $STATS_PAGE->hide;
382 root 1.60 destroy_query_dialog $conn;
383 root 1.74 0
384 root 1.60 },
385     );
386    
387 root 1.104 unshift @dialog, my $hbox = new CFPlus::UI::HBox;
388 root 1.98 for (
389     [Str => CS_STAT_STR],
390     [Dex => CS_STAT_DEX],
391     [Con => CS_STAT_CON],
392     [Int => CS_STAT_INT],
393     [Wis => CS_STAT_WIS],
394     [Pow => CS_STAT_POW],
395     [Cha => CS_STAT_CHA],
396     ) {
397     my ($name, $id) = @$_;
398 root 1.104 $hbox->add (new CFPlus::UI::Label
399 root 1.98 markup => "$conn->{stat}{$id} <span foreground='yellow'>$name</span>",
400     align => 0,
401     expand => 1,
402     can_events => 1,
403     can_hover => 1,
404 root 1.107 tooltip => "#stat_$name",
405 root 1.98 );
406     }
407    
408 root 1.104 unshift @dialog, new CFPlus::UI::Label
409 root 1.60 max_w => $::WIDTH * 0.4,
410     ellipsise => 0,
411 root 1.109 markup => (CFPlus::Pod::section_label ui => "chargen_stats"),
412 root 1.60 ;
413     }
414    
415 root 1.104 push @dialog, my $entry = new CFPlus::UI::Entry
416 root 1.60 on_changed => sub {
417     $conn->send ("reply $_[1]");
418     destroy_query_dialog $conn;
419 root 1.74 0
420 root 1.60 },
421     ;
422    
423 root 1.74 $entry->grab_focus;
424 root 1.60
425     } else {
426     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
427    
428 root 1.104 push @dialog, my $entry = new CFPlus::UI::Entry
429 root 1.60 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
430     on_activate => sub {
431     $conn->send ("reply $_[1]");
432     destroy_query_dialog $conn;
433 root 1.74 0
434 root 1.60 },
435     ;
436    
437 root 1.74 $entry->grab_focus;
438 root 1.60 }
439    
440     $vbox->add (@dialog);
441     $dialog->show;
442     }
443    
444 root 1.1 sub start_game {
445     status "logging in...";
446    
447 root 1.23 $LOGIN_BUTTON->set_text ("Logout");
448 root 1.49 $SETUP_DIALOG->hide;
449 root 1.23
450 root 1.146 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
451 root 1.1
452 root 1.120 my ($host, $port) = split /:/, $PROFILE->{host};
453 root 1.11
454 root 1.124 $MAP = new CFPlus::Map;
455 root 1.1
456     $CONN = eval {
457 root 1.104 new CFPlus::Protocol
458 root 1.1 host => $host,
459     port => $port || 13327,
460 root 1.120 user => $PROFILE->{user},
461     pass => $PROFILE->{password},
462 root 1.1 mapw => $mapsize,
463     maph => $mapsize,
464 root 1.11
465 root 1.121 client => "cfplus $CFPlus::VERSION $] $^O",
466 root 1.118
467 root 1.11 map_widget => $MAPWIDGET,
468     logview => $LOGVIEW,
469     statusbox => $STATUSBOX,
470     map => $MAP,
471     mapmap => $MAPMAP,
472 root 1.60 query => \&server_query,
473 root 1.11
474 root 1.149 setup_req => {
475     smoothing => $CFG->{map_smoothing}*1,
476     },
477    
478 root 1.11 sound_play => sub {
479     my ($x, $y, $soundnum, $type) = @_;
480    
481     $SDL_MIXER
482     or return;
483    
484     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
485     or return;
486    
487     $chunk->play;
488     },
489 root 1.1 };
490    
491     if ($CONN) {
492 root 1.104 CFPlus::lowdelay fileno $CONN->{fh};
493 root 1.1
494     status "login successful";
495     } else {
496     status "unable to connect";
497     stop_game();
498     }
499     }
500    
501     sub stop_game {
502 root 1.23 $LOGIN_BUTTON->set_text ("Login");
503 root 1.53 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
504 root 1.49 $SETUP_DIALOG->show;
505 elmex 1.85 $PL_WINDOW->hide;
506 root 1.132 $SPELL_LIST->clear_spells;
507 root 1.156 $CFPlus::UI::ROOT->emit (stop_game => ! ! $CONN);
508 root 1.23
509 root 1.166 &audio_music_set ([]);
510    
511 root 1.1 return unless $CONN;
512    
513     status "connection closed";
514 root 1.23
515 root 1.60 destroy_query_dialog $CONN;
516 root 1.1 $CONN->destroy;
517     $CONN = 0; # false, does not autovivify
518 root 1.76
519     undef $MAP;
520 root 1.1 }
521    
522 root 1.49 sub graphics_setup {
523 root 1.104 my $vbox = new CFPlus::UI::VBox;
524 root 1.30
525 root 1.104 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
526 root 1.1
527 root 1.144 my $row = 0;
528    
529     $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "OpenGL Info");
530     $table->add (1, $row++, new CFPlus::UI::Label valign => 0, fontsize => 0.8, text => CFPlus::OpenGL::gl_vendor . ", " . CFPlus::OpenGL::gl_version,
531     can_events => 1,
532     tooltip => "<tt><span size='8192'>" . (CFPlus::OpenGL::gl_extensions) . "</span></tt>");
533    
534     $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Video Mode");
535     $table->add (1, $row++, my $hbox = new CFPlus::UI::HBox);
536 root 1.1
537 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]);
538     $hbox->add (my $mode_label = new CFPlus::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
539 root 1.1
540     $mode_slider->connect (changed => sub {
541     my ($self, $value) = @_;
542    
543     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
544     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
545     });
546     $mode_slider->emit (changed => $mode_slider->{range}[0]);
547    
548 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fullscreen");
549     $table->add (1, $row++, $FULLSCREEN_ENABLE = new CFPlus::UI::CheckBox
550 root 1.1 state => $CFG->{fullscreen},
551     tooltip => "Bring the client into fullscreen mode.",
552 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
553 root 1.1 );
554    
555 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
556     $table->add (1, $row++, new CFPlus::UI::CheckBox
557 root 1.1 state => $CFG->{fast},
558     tooltip => "Lower the visual quality considerably to speed up rendering.",
559 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
560 root 1.1 );
561    
562 root 1.149 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
563     $table->add (1, $row++, new CFPlus::UI::Slider
564     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
565     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
566     on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
567     );
568    
569     $table->add (1, $row++, new CFPlus::UI::Button
570     expand => 1, align => 0, text => "Apply",
571     tooltip => "Apply the video settings above.",
572     on_activate => sub {
573     video_shutdown ();
574     video_init ();
575     0
576     }
577     );
578    
579 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Scale");
580     $table->add (1, $row++, new CFPlus::UI::Slider
581 root 1.1 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
582     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
583 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
584 root 1.1 );
585    
586 root 1.149 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Smoothing");
587     $table->add (1, $row++, new CFPlus::UI::CheckBox
588     state => $CFG->{map_smoothing},
589     tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
590     . "This increases load on the graphics subsystem and works only with 2.x servers. "
591     . "Changes take effect at next connection only.",
592     on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
593     );
594    
595 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fog of War");
596     $table->add (1, $row++, new CFPlus::UI::CheckBox
597 root 1.1 state => $CFG->{fow_enable},
598     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
599 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
600 root 1.1 );
601    
602 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "FoW Intensity");
603     $table->add (1, $row++, new CFPlus::UI::Slider
604 root 1.1 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
605     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
606 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
607 root 1.1 );
608    
609 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "FoW Smooth");
610     $table->add (1, $row++, new CFPlus::UI::CheckBox
611 root 1.1 state => $CFG->{fow_smooth},
612     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
613 root 1.18 on_changed => sub {
614 root 1.1 my ($self, $value) = @_;
615     $CFG->{fow_smooth} = $value;
616 root 1.104 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFPlus::OpenGL::GL_VERSION < 1.2;
617 root 1.74 0
618 root 1.1 }
619     );
620    
621 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Message Fontsize");
622     $table->add (1, $row++, new CFPlus::UI::Slider
623 root 1.1 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
624     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
625 root 1.74 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
626 root 1.1 );
627    
628 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
629     $table->add (1, $row++, new CFPlus::UI::Slider
630 root 1.1 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
631     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
632 root 1.18 on_changed => sub {
633 root 1.1 $CFG->{gauge_fontsize} = $_[1];
634     &set_gauge_window_fontsize;
635 root 1.74 0
636 root 1.1 }
637     );
638    
639 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge size");
640     $table->add (1, $row++, new CFPlus::UI::Slider
641 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
642     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
643     on_changed => sub {
644 root 1.1 $CFG->{gauge_size} = $_[1];
645     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
646 root 1.74 0
647 root 1.1 }
648     );
649    
650 root 1.49 $vbox
651     }
652    
653     sub audio_setup {
654 root 1.104 my $vbox = new CFPlus::UI::VBox;
655 root 1.49
656 root 1.104 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
657 root 1.49
658     my $row = 0;
659    
660 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable");
661     $table->add (1, $row++, new CFPlus::UI::CheckBox
662 root 1.1 state => $CFG->{audio_enable},
663     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.",
664 root 1.74 on_changed => sub { $CFG->{audio_enable} = $_[1]; 0 }
665 root 1.1 );
666 root 1.104 # $table->add (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Effects Volume");
667     # $table->add (1, 8, new CFPlus::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
668 root 1.1 # $CFG->{effects_volume} = $_[1];
669     # });
670 root 1.104 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music");
671     $table->add (1, $row++, my $hbox = new CFPlus::UI::HBox);
672     $hbox->add (new CFPlus::UI::CheckBox
673 root 1.1 expand => 1, state => $CFG->{bgm_enable},
674     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
675 root 1.74 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 }
676 root 1.1 );
677 root 1.104 $hbox->add (new CFPlus::UI::Slider
678 root 1.1 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
679     tooltip => "The volume of the background music. Changes are instant.",
680 root 1.104 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFPlus::MixMusic::volume $_[1] * 128; 0 }
681 root 1.1 );
682    
683 root 1.104 $table->add (1, $row++, new CFPlus::UI::Button
684 root 1.1 expand => 1, align => 0, text => "Apply",
685     tooltip => "Apply the audio settings",
686 root 1.18 on_activate => sub {
687 root 1.1 audio_shutdown ();
688     audio_init ();
689 root 1.74 0
690 root 1.1 }
691     );
692    
693 root 1.49 $vbox
694 root 1.1 }
695    
696     sub set_gauge_window_fontsize {
697     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
698     $_->set_fontsize ($::CFG->{gauge_fontsize});
699     }
700     }
701    
702     sub make_gauge_window {
703     my $gh = int $HEIGHT * $CFG->{gauge_size};
704    
705 root 1.104 my $win = new CFPlus::UI::Frame (
706 root 1.30 force_x => 0,
707     force_y => "max",
708     force_w => $WIDTH,
709     force_h => $gh,
710 root 1.1 );
711    
712 root 1.104 $win->add (my $hbox = new CFPlus::UI::HBox
713 root 1.1 children => [
714 root 1.104 (new CFPlus::UI::HBox expand => 1),
715     (new CFPlus::UI::VBox children => [
716     (new CFPlus::UI::Empty expand => 1),
717     (new CFPlus::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFPlus::UI::Table)),
718 root 1.1 ]),
719 root 1.104 (my $vbox = new CFPlus::UI::VBox),
720 root 1.1 ],
721     );
722    
723 root 1.104 $vbox->add (new CFPlus::UI::HBox
724 root 1.1 expand => 1,
725     children => [
726 root 1.104 (new CFPlus::UI::Empty expand => 1),
727     (my $hb = new CFPlus::UI::HBox),
728 root 1.1 ],
729     );
730    
731 root 1.109 $hb->add (my $hg = new CFPlus::UI::Gauge type => 'hp', tooltip => "#stat_health");
732     $hb->add (my $mg = new CFPlus::UI::Gauge type => 'mana', tooltip => "#stat_mana");
733     $hb->add (my $gg = new CFPlus::UI::Gauge type => 'grace', tooltip => "#stat_grace");
734     $hb->add (my $fg = new CFPlus::UI::Gauge type => 'food', tooltip => "#stat_food");
735    
736     $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
737     $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
738 root 1.1
739     $GAUGES = {
740     exp => $exp, win => $win, range => $rng,
741     food => $fg, mana => $mg, hp => $hg, grace => $gg
742     };
743    
744     &set_gauge_window_fontsize;
745    
746     $win
747     }
748    
749 root 1.65 sub debug_setup {
750 root 1.104 my $table = new CFPlus::UI::Table;
751 root 1.65
752 root 1.104 $table->add (0, 0, new CFPlus::UI::Label text => "Widget Borders");
753     $table->add (1, 0, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
754     $table->add (0, 1, new CFPlus::UI::Label text => "Tooltip Widget Info");
755     $table->add (1, 1, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
756     $table->add (0, 2, new CFPlus::UI::Label text => "Show FPS");
757     $table->add (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
758     $table->add (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips");
759     $table->add (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
760 root 1.158 $table->add (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } );
761 root 1.65
762     my @default_smooth = (0.05, 0.13, 0.05, 0.13, 0.30, 0.13, 0.05, 0.13, 0.05);
763    
764     for my $x (0..2) {
765     for my $y (0 .. 2) {
766     $table->add ($x + 3, $y,
767 root 1.104 new CFPlus::UI::Entry
768 root 1.65 text => $default_smooth[$x * 3 + $y],
769     on_changed => sub { $MAP->{smooth_matrix}[$x * 3 + $y] = $_[1] if $MAP; 0 },
770     );
771     }
772     }
773    
774 root 1.136 $table->add (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
775 root 1.65
776     $table
777     }
778 elmex 1.24
779 root 1.60 sub stats_window {
780 root 1.104 my $r = new CFPlus::UI::ScrolledWindow (
781 root 1.98 expand => 1,
782     scroll_y => 1
783     );
784 root 1.104 $r->add (my $vb = new CFPlus::UI::VBox);
785 root 1.1
786 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
787     label => "Player",
788     child => (my $pi = new CFPlus::UI::VBox),
789     );
790    
791     $pi->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
792 root 1.1 can_hover => 1, can_events => 1,
793     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
794 root 1.114 $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
795 root 1.1 can_hover => 1, can_events => 1,
796     tooltip => "The map you are currently on (if supported by the server).");
797    
798 root 1.114 $pi->add (my $hb0 = new CFPlus::UI::HBox);
799 root 1.104 $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
800 elmex 1.5 can_hover => 1, can_events => 1,
801 root 1.15 tooltip => "The weight of the player including all inventory items.");
802 root 1.104 $hb0->add ($STATWIDS->{m_weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
803 elmex 1.5 can_hover => 1, can_events => 1,
804 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
805 elmex 1.5
806 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
807     label => "Primary/Secondary Statistics",
808     child => (my $hb = new CFPlus::UI::HBox expand => 1),
809     );
810 root 1.104 $hb->add (my $tbl = new CFPlus::UI::Table expand => 1);
811 root 1.1
812     my $color2 = [1, 1, 0];
813    
814     for (
815 root 1.98 [0, 0, st_str => "Str", 30],
816     [0, 1, st_dex => "Dex", 30],
817     [0, 2, st_con => "Con", 30],
818     [0, 3, st_int => "Int", 30],
819     [0, 4, st_wis => "Wis", 30],
820     [0, 5, st_pow => "Pow", 30],
821     [0, 6, st_cha => "Cha", 30],
822    
823     [2, 0, st_wc => "Wc", -120],
824     [2, 1, st_ac => "Ac", -120],
825     [2, 2, st_dam => "Dam", 120],
826     [2, 3, st_arm => "Arm", 120],
827     [2, 4, st_spd => "Spd", 10.54],
828     [2, 5, st_wspd => "WSp", 10.54],
829 root 1.1 ) {
830 root 1.98 my ($col, $row, $id, $label, $template) = @$_;
831 root 1.1
832 root 1.104 $tbl->add ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label
833 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0,
834 root 1.107 align => +1, template => $template, tooltip => "#stat_$label");
835 root 1.104 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label
836 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0,
837 root 1.107 align => -1, text => $label, tooltip => "#stat_$label");
838 root 1.1 }
839    
840 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
841     label => "Resistancies",
842     child => (my $tbl2 = new CFPlus::UI::Table expand => 1),
843     );
844 root 1.1
845     my $row = 0;
846     my $col = 0;
847    
848     my %resist_names = (
849 elmex 1.92 slow => ["Slow",
850     "<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.)"],
851     holyw => ["Holy Word",
852     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
853     conf => ["Confusion",
854     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
855     fire => ["Fire",
856     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
857     depl => ["Depletion",
858     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
859     magic => ["Magic",
860     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
861     drain => ["Draining",
862     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
863     acid => ["Acid",
864     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
865     pois => ["Poison",
866     "<b>Poison</b> (resistance to getting poisoned)"],
867     para => ["Paralysation",
868     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
869     deat => ["Death",
870     "<b>Death</b> (resistance against death spells)"],
871     phys => ["Physical",
872     "<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.)"],
873     blind => ["Blind",
874     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
875     fear => ["Fear",
876     "<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)"],
877     tund => ["Turn undead",
878     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
879     elec => ["Electricity",
880     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
881     cold => ["Cold",
882     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
883     ghit => ["Ghost hit",
884     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
885 root 1.1 );
886     for (qw/slow holyw conf fire depl magic
887     drain acid pois para deat phys
888     blind fear tund elec cold ghit/)
889     {
890     $tbl2->add ($col, $row,
891     $STATWIDS->{"res_$_"} =
892 root 1.104 new CFPlus::UI::Label
893 root 1.1 font => $FONT_FIXED,
894     template => "-100%",
895     align => +1,
896     valign => 0,
897     can_events => 1,
898     can_hover => 1,
899 elmex 1.92 tooltip => $resist_names{$_}->[1],
900 root 1.1 );
901 root 1.104 $tbl2->add ($col + 1, $row, new CFPlus::UI::Image
902 root 1.1 font => $FONT_FIXED,
903     can_hover => 1,
904     can_events => 1,
905 root 1.78 path => "ui/resist/resist_$_.png",
906 elmex 1.92 tooltip => $resist_names{$_}->[1],
907     );
908 root 1.104 $tbl2->add ($col + 2, $row, new CFPlus::UI::Label
909 elmex 1.92 text => $resist_names{$_}->[0],
910     font => $FONT_FIXED,
911     can_hover => 1,
912     can_events => 1,
913     tooltip => $resist_names{$_}->[1],
914 root 1.1 );
915    
916     $row++;
917     if ($row % 6 == 0) {
918 elmex 1.92 $col += 3;
919 root 1.1 $row = 0;
920     }
921     }
922    
923 root 1.95 #update_stats_window ({});
924 root 1.1
925 elmex 1.97 $r
926 root 1.1 }
927    
928 elmex 1.92 sub skill_window {
929 root 1.104 my $sw = new CFPlus::UI::ScrolledWindow (expand => 1);
930     $sw->add ($STATWIDS->{skill_tbl} = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1, 0, 0, 1]);
931 elmex 1.97 $sw
932 elmex 1.92 }
933    
934 root 1.48 sub formsep($) {
935     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
936 root 1.1 }
937    
938     my $METASERVER_ATIME;
939    
940     sub update_metaserver {
941 elmex 1.81 my ($metaserver_dialog) = @_;
942    
943     $METASERVER = $metaserver_dialog
944     if defined $metaserver_dialog;
945    
946 root 1.1 return if $METASERVER_ATIME > time;
947     $METASERVER_ATIME = time + 60;
948    
949     my $table = $METASERVER->{table};
950     $table->clear;
951 root 1.104 $table->add (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
952 root 1.1
953 root 1.125 my $ok = 0;
954 root 1.1
955 root 1.125 CFPlus::background {
956     my $ua = CFPlus::lwp_useragent;
957 root 1.1
958 root 1.125 CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content;
959     } sub {
960     my ($msg) = @_;
961     if ($msg) {
962 root 1.1 $table->clear;
963    
964 root 1.62 my @tip = (
965     "The current number of users logged in on the server.",
966     "The hostname of the server.",
967     "The time this server has been running without being restarted.",
968     "The server software version - a '+' indicates a Crossfire+ server.",
969     "Short information about this server provided by its admins.",
970     );
971     my @col = qw(#Users Host Uptime Version Description);
972 root 1.104 $table->add ($_, 0, new CFPlus::UI::Label
973 root 1.62 can_hover => 1, can_events => 1,
974     align => 0, fg => [1, 1, 0],
975     text => $col[$_], tooltip => $tip[$_])
976     for 0 .. $#col;
977 root 1.1
978     my @align = qw(1 0 1 1 -1);
979    
980     my $y = 0;
981 root 1.142 for my $m (@{ $msg->{servers} }) {
982     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
983     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
984 root 1.1
985     for ($desc) {
986     s/<br>/\n/gi;
987     s/<li>/\n· /gi;
988     s/<.*?>//sgi;
989 root 1.125 s/&amp;/&/g;
990     s/&lt;/</g;
991     s/&gt;/>/g;
992 root 1.1 }
993    
994     $uptime = sprintf "%dd %02d:%02d:%02d",
995 root 1.125 (int $uptime / 86400),
996     (int $uptime / 3600) % 24,
997     (int $uptime / 60) % 60,
998     $uptime % 60;
999 root 1.1
1000     $m = [$users, $host, $uptime, $version, $desc];
1001    
1002     $y++;
1003    
1004 root 1.104 $table->add (scalar @$m, $y, new CFPlus::UI::VBox children => [
1005     (new CFPlus::UI::Button
1006 root 1.62 text => "Use",
1007     tooltip => "Put this server into the <b>Host:Port</b> field",
1008     on_activate => sub {
1009 root 1.75 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1010 root 1.62 $METASERVER->hide;
1011 root 1.74 0
1012 root 1.62 },
1013     ),
1014 root 1.104 (new CFPlus::UI::Empty expand => 1),
1015 root 1.1 ]);
1016    
1017 root 1.104 $table->add ($_, $y, new CFPlus::UI::Label
1018 root 1.125 max_w => $::WIDTH * 0.4,
1019 root 1.62 ellipsise => 0,
1020     align => $align[$_],
1021     text => $m->[$_],
1022     tooltip => $tip[$_],
1023 root 1.142 fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1024 root 1.62 can_hover => 1,
1025     can_events => 1,
1026     fontsize => 0.8)
1027 root 1.1 for 0 .. $#$m;
1028     }
1029 root 1.125 } else {
1030     $ok or $label->set_text ("error while contacting metaserver");
1031 root 1.1 }
1032 root 1.125 };
1033    
1034 root 1.1 }
1035    
1036 root 1.40 sub metaserver_dialog {
1037 root 1.104 my $vbox = new CFPlus::UI::VBox;
1038     my $table = new CFPlus::UI::Table;
1039     $vbox->add (new CFPlus::UI::ScrolledWindow expand => 1, child => $table);
1040 elmex 1.81
1041 root 1.114 my $dialog = new CFPlus::UI::Toplevel
1042 root 1.62 title => "Server List",
1043     name => 'metaserver_dialog',
1044     x => 'center',
1045     y => 'center',
1046     z => 3,
1047 root 1.125 force_w => $::WIDTH * 0.9,
1048     force_h => $::HEIGHT * 0.7,
1049 elmex 1.81 child => $vbox,
1050 root 1.80 has_close_button => 1,
1051 elmex 1.81 table => $table,
1052 root 1.40 on_visibility_change => sub {
1053 elmex 1.81 update_metaserver ($_[0]) if $_[1];
1054 root 1.74 0
1055 root 1.40 },
1056     ;
1057    
1058     $dialog
1059     }
1060    
1061 root 1.1 sub server_setup {
1062 root 1.104 my $vbox = new CFPlus::UI::VBox;
1063 elmex 1.19
1064 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1065     label => "Connection Settings",
1066     child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]),
1067     );
1068 root 1.104 $table->add (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
1069 root 1.1
1070     {
1071 root 1.104 $table->add (1, 2, my $vbox = new CFPlus::UI::VBox);
1072 root 1.1
1073     $vbox->add (
1074 root 1.104 $HOST_ENTRY = new CFPlus::UI::Entry
1075 root 1.1 expand => 1,
1076 root 1.75 text => $CFG->{profile}{default}{host},
1077 root 1.1 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1078 root 1.18 on_changed => sub {
1079 root 1.1 my ($self, $value) = @_;
1080 root 1.75 $CFG->{profile}{default}{host} = $value;
1081 root 1.74 0
1082 root 1.1 }
1083     );
1084    
1085 root 1.104 $vbox->add (new CFPlus::UI::Button
1086 root 1.40 expand => 1,
1087     text => "Server List",
1088     other => $METASERVER,
1089 root 1.1 tooltip => "Show a list of available crossfire servers",
1090 root 1.74 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1091     on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 },
1092 root 1.1 );
1093     }
1094    
1095 root 1.104 $table->add (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
1096     $table->add (1, 4, new CFPlus::UI::Entry
1097 root 1.75 text => $CFG->{profile}{default}{user},
1098 root 1.1 tooltip => "The name of your character on the server",
1099 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value }
1100 root 1.1 );
1101    
1102 root 1.104 $table->add (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
1103     $table->add (1, 5, new CFPlus::UI::Entry
1104 root 1.75 text => $CFG->{profile}{default}{password},
1105 root 1.1 hidden => 1,
1106     tooltip => "The password for your character",
1107 root 1.75 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value }
1108 root 1.1 );
1109    
1110 root 1.104 $table->add (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1111     $table->add (1, 7, new CFPlus::UI::Slider
1112 root 1.30 force_w => 100,
1113 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1114     tooltip => "This is the size of the portion of the map update the server sends you. "
1115     . "If you set this to a high value you will be able to see further, "
1116     . "but you also increase bandwidth requirements and latency. "
1117     . "This option is only used once at log-in.",
1118 root 1.74 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 },
1119 root 1.1 );
1120    
1121 root 1.104 $table->add (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1122     $table->add (1, 8, new CFPlus::UI::CheckBox
1123 root 1.1 state => $CFG->{face_prefetch},
1124     tooltip => "<b>Background Image Prefetch</b>\n\n"
1125     . "If enabled, the client automatically pre-fetches images from the server. "
1126     . "This might increase or create lag, but increases the chances "
1127     . "of faces being ready for display when you encounter them. "
1128     . "It also uses up server bandwidth on every connect, "
1129     . "so only set it if you really need to prefetch images. "
1130     . "This option can be set and unset any time.",
1131 root 1.74 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
1132 root 1.1 );
1133    
1134 root 1.143 $table->add (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
1135 root 1.104 $table->add (1, 9, new CFPlus::UI::Entry
1136 root 1.143 text => $CFG->{output_rate},
1137     tooltip => "The approximate bandwidth in bytes per second that the server should not exceed "
1138     . "when sending images, to ensure interactiveness. When 0 or unset, the server "
1139     . "default will be used, which is usually around 100kb/s.",
1140     on_changed => sub { $CFG->{output_rate} = $_[1]; 0 },
1141     );
1142    
1143     $table->add (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1144     $table->add (1, 10, new CFPlus::UI::Entry
1145 root 1.1 text => $CFG->{output_count},
1146     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1147 root 1.74 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
1148 root 1.1 );
1149    
1150 root 1.143 $table->add (0, 11, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1151     $table->add (1, 11, new CFPlus::UI::Entry
1152 root 1.1 text => $CFG->{output_sync},
1153     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1154 root 1.74 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
1155 root 1.1 );
1156    
1157 root 1.143 $table->add (1, 12, $LOGIN_BUTTON = new CFPlus::UI::Button
1158 root 1.1 expand => 1,
1159     align => 0,
1160     text => "Login",
1161 root 1.18 on_activate => sub {
1162 root 1.1 $CONN ? stop_game
1163     : start_game;
1164 root 1.74 0
1165 root 1.1 },
1166     );
1167    
1168 root 1.149 $vbox->add (new CFPlus::UI::FancyFrame
1169     label => "Server Info",
1170     child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1171     );
1172    
1173     $vbox
1174     }
1175    
1176     sub client_setup {
1177     my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
1178    
1179     my $row = 0;
1180    
1181     $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Chat Command");
1182     $table->add (1, $row++, my $saycmd = new CFPlus::UI::Entry
1183 root 1.119 text => $CFG->{say_command},
1184 root 1.49 tooltip => "This is the command that will be used if you write a line in the message window entry or press <b>\"</b> in the map window. "
1185 root 1.149 . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
1186     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
1187 root 1.49 on_changed => sub {
1188     my ($self, $value) = @_;
1189     $CFG->{say_command} = $value;
1190 root 1.74 0
1191 root 1.49 }
1192     );
1193    
1194 root 1.149 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1195     $table->add (1, $row++, new CFPlus::UI::CheckBox
1196 root 1.119 state => $CFG->{show_tips},
1197     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1198     on_changed => sub {
1199     my ($self, $value) = @_;
1200 root 1.121 $CFG->{show_tips} = $value;
1201 root 1.119 0
1202     }
1203     );
1204    
1205 root 1.149 $table->add (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1206     $table->add (1, $row++, my $saycmd = new CFPlus::UI::Entry
1207     text => $CFG->{logview_max_par},
1208     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1209     . "sends more messages than this number, older messages get removed to save memory and "
1210     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1211     on_changed => sub {
1212     my ($self, $value) = @_;
1213     $LOGVIEW->{max_par} = $CFG->{logview_max_par} = $value*1;
1214     0
1215     },
1216 root 1.67 );
1217    
1218 root 1.149 $table
1219 root 1.1 }
1220    
1221     sub message_window {
1222 root 1.114 my $window = new CFPlus::UI::Toplevel
1223 elmex 1.16 name => "message_window",
1224 root 1.1 title => "Messages",
1225     border_bg => [1, 1, 1, 1],
1226 root 1.30 x => "max",
1227     y => 0,
1228 root 1.60 force_w => $::WIDTH * 0.4,
1229     force_h => $::HEIGHT * 0.5,
1230 root 1.104 child => (my $vbox = new CFPlus::UI::VBox),
1231 root 1.74 has_close_button => 1;
1232 root 1.1
1233     $vbox->add ($LOGVIEW);
1234    
1235 root 1.104 $vbox->add (my $input = new CFPlus::UI::Entry
1236 root 1.1 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
1237     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
1238     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
1239     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
1240 root 1.18 on_focus_in => sub {
1241 root 1.1 my ($input, $prev_focus) = @_;
1242    
1243     delete $input->{refocus_map};
1244    
1245     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
1246     $input->{refocus_map} = 1;
1247     }
1248     delete $input->{auto_activated};
1249 root 1.74
1250     0
1251 root 1.1 },
1252 root 1.18 on_activate => sub {
1253 root 1.1 my ($input, $text) = @_;
1254     $input->set_text ('');
1255    
1256 elmex 1.46 if ($text =~ /^\/(.*)/) {
1257 root 1.1 $::CONN->user_send ($1);
1258     } else {
1259     my $say_cmd = $::CFG->{say_command} || 'say';
1260     $::CONN->user_send ("$say_cmd $text");
1261     }
1262     if ($input->{refocus_map}) {
1263     delete $input->{refocus_map};
1264     $MAPWIDGET->focus_in
1265     }
1266 root 1.74
1267     0
1268 root 1.1 },
1269 root 1.18 on_escape => sub {
1270 root 1.74 $MAPWIDGET->grab_focus;
1271    
1272     0
1273 root 1.1 },
1274     );
1275    
1276     $CONSOLE = {
1277     window => $window,
1278 root 1.30 input => $input,
1279 root 1.1 };
1280    
1281     $window
1282     }
1283    
1284 root 1.49 sub autopickup_setup {
1285 root 1.170 my $r = new CFPlus::UI::ScrolledWindow (
1286     expand => 1,
1287     scroll_y => 1
1288     );
1289     $r->add (my $table = new CFPlus::UI::Table
1290     row_expand => [0],
1291     col_expand => [0, 1, 0, 1],
1292     );
1293 elmex 1.44
1294 elmex 1.43 for (
1295 root 1.51 ["General", 0, 0,
1296 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1297 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1298     ["Stop before pickup" => PICKUP_STOP],
1299     ["Debug autopickup" => PICKUP_DEBUG],
1300 root 1.51 ],
1301     ["Weapons", 0, 6,
1302 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1303     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1304     ["Bows" => PICKUP_BOW],
1305     ["Arrows" => PICKUP_ARROW],
1306 root 1.51 ],
1307     ["Armour", 0, 12,
1308 root 1.58 ["Helmets" => PICKUP_HELMET],
1309     ["Shields" => PICKUP_SHIELD],
1310     ["Body Armour" => PICKUP_ARMOUR],
1311     ["Boots" => PICKUP_BOOTS],
1312     ["Gloves" => PICKUP_GLOVES],
1313     ["Cloaks" => PICKUP_CLOAK],
1314 root 1.51 ],
1315    
1316 root 1.133 ["Readables", 2, 0,
1317 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1318     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1319     ["Normal Books/Scrolls" => PICKUP_READABLES],
1320 root 1.51 ],
1321 root 1.133 ["Misc", 2, 5,
1322 root 1.58 ["Food" => PICKUP_FOOD],
1323     ["Drinks" => PICKUP_DRINK],
1324     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1325     ["Keys" => PICKUP_KEY],
1326     ["Magical Items" => PICKUP_MAGICAL],
1327     ["Potions" => PICKUP_POTION],
1328     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1329     ["Ignore cursed" => PICKUP_NOT_CURSED],
1330     ["Jewelery" => PICKUP_JEWELS],
1331 root 1.133 ["Flesh" => PICKUP_FLESH],
1332 root 1.51 ],
1333 elmex 1.66 ["Weight/Value ratio", 2, 17]
1334 elmex 1.43 )
1335     {
1336 root 1.51 my ($title, $x, $y, @bits) = @$_;
1337 root 1.104 $table->add ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1338 root 1.51
1339     for (@bits) {
1340     ++$y;
1341    
1342 elmex 1.43 my $mask = $_->[1];
1343 root 1.104 $table->add ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1344     $table->add ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1345 elmex 1.83 state => $::CFG->{pickup} & $mask,
1346 elmex 1.43 on_changed => sub {
1347     my ($box, $value) = @_;
1348 root 1.63
1349 elmex 1.43 if ($value) {
1350 elmex 1.45 $::CFG->{pickup} |= $mask;
1351 elmex 1.43 } else {
1352 root 1.63 $::CFG->{pickup} &= ~$mask;
1353 elmex 1.43 }
1354 root 1.63
1355     $::CONN->send_command ("pickup $::CFG->{pickup}")
1356 elmex 1.45 if defined $::CONN;
1357 root 1.74
1358     0
1359 elmex 1.43 });
1360 root 1.86
1361     ${$_->[2]} = $checkbox if $_->[2];
1362 elmex 1.43 }
1363     }
1364    
1365 root 1.104 $table->add (2, 18, new CFPlus::UI::ValSlider
1366 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1367     template => ">= 99",
1368 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1369     on_changed => sub {
1370     my ($slider, $value) = @_;
1371    
1372 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1373 elmex 1.66 $::CFG->{pickup} |= int $value
1374     if $value;
1375     1;
1376     });
1377 elmex 1.83
1378 root 1.104 $table->add (3, 18, new CFPlus::UI::Button
1379 elmex 1.66 text => "set",
1380     on_activate => sub {
1381     $::CONN->send_command ("pickup $::CFG->{pickup}")
1382     if defined $::CONN;
1383 root 1.74 0
1384 elmex 1.66 });
1385    
1386 root 1.170 $r
1387 elmex 1.43 }
1388    
1389 root 1.102 my %SORT_ORDER = (
1390     type => undef,
1391 root 1.130 mtime => sub {
1392     my $NOW = time;
1393     sort {
1394     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1395     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1396    
1397     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1398     or $btime <=> $atime
1399     or $a->{type} <=> $b->{type}
1400     } @_
1401     },
1402 root 1.102 weight => sub { sort {
1403     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1404     or $a->{type} <=> $b->{type}
1405     } @_ },
1406     );
1407    
1408 elmex 1.85 sub inventory_widget {
1409 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1410 root 1.1
1411 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1412     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1413 root 1.99
1414 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1415 root 1.99
1416     use sort 'stable';
1417    
1418 root 1.104 $hb1->add (new CFPlus::UI::Selector
1419 root 1.102 value => $::CFG->{inv_sort},
1420 root 1.99 options => [
1421 root 1.102 [type => "Type/Name"],
1422     [mtime => "Recent/Normal/Locked"],
1423     [weight => "Weight/Type"],
1424 root 1.99 ],
1425     on_changed => sub {
1426 root 1.102 $::CFG->{inv_sort} = $_[1];
1427     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1428 root 1.99 },
1429     );
1430 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1431 root 1.99 #TODO# update to weigh/maxweight
1432 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1433 root 1.99
1434 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1435     $sw1->add ($INV = new CFPlus::UI::Inventory);
1436 root 1.127 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1437 root 1.1
1438 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1439 elmex 1.17
1440 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1441 elmex 1.14
1442 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1443     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1444 root 1.1
1445 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1446 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1447 elmex 1.27
1448 elmex 1.85 $hb
1449 root 1.1 }
1450    
1451 root 1.86 sub toggle_player_page {
1452     my ($widget) = @_;
1453    
1454     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1455     $PL_WINDOW->hide;
1456     } else {
1457     $PL_NOTEBOOK->set_current_page ($widget);
1458     $PL_WINDOW->show;
1459     }
1460     }
1461    
1462 elmex 1.85 sub player_window {
1463 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1464 elmex 1.85 x => "center",
1465     y => "center",
1466     force_w => $WIDTH * 9/10,
1467     force_h => $HEIGHT * 9/10,
1468     title => "Player",
1469 elmex 1.90 name => "playerbook",
1470 elmex 1.85 has_close_button => 1
1471     ;
1472    
1473     my $ntb =
1474     $PL_NOTEBOOK =
1475 root 1.157 new CFPlus::UI::Notebook expand => 1;
1476 root 1.86
1477 elmex 1.85 $ntb->add (
1478 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1479 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1480     );
1481     $ntb->add (
1482 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1483 elmex 1.92 "Shows all your Skills."
1484 elmex 1.85 );
1485 elmex 1.97
1486 root 1.131 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1487     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1488 elmex 1.85 $ntb->add (
1489 elmex 1.97 "Spellbook (F4)" => $spellsw,
1490 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1491 elmex 1.85 );
1492     $ntb->add (
1493 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1494 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1495     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1496 elmex 1.85 );
1497 root 1.155 $ntb->add (Pickup => autopickup_setup,
1498     "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1499 elmex 1.85
1500 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1501 root 1.86
1502 elmex 1.85 $plwin->add ($ntb);
1503     $plwin
1504 elmex 1.38 }
1505    
1506 root 1.49 sub keyboard_setup {
1507 root 1.137 CFPlus::Macro::keyboard_setup
1508 elmex 1.24 }
1509    
1510 root 1.64 sub help_window {
1511 root 1.114 my $win = new CFPlus::UI::Toplevel
1512 root 1.41 x => 'center',
1513     y => 'center',
1514 root 1.119 z => 4,
1515 root 1.41 name => 'doc_browser',
1516     force_w => int $WIDTH * 7/8,
1517     force_h => int $HEIGHT * 7/8,
1518 root 1.87 title => "Help Browser",
1519     has_close_button => 1;
1520 root 1.1
1521 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1522 root 1.1
1523 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1524     label => "Navigation",
1525     child => (my $buttons = new CFPlus::UI::HBox),
1526     );
1527 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1528 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1529 root 1.1
1530 root 1.111 my @history;
1531     my @future;
1532 root 1.112 my $curnode;
1533 root 1.111
1534     my $load_node; $load_node = sub {
1535 root 1.115 my ($node, $para) = @_;
1536 root 1.111
1537     $buttons->clear;
1538    
1539 root 1.116 $buttons->add (new CFPlus::UI::Button
1540     text => "⇤",
1541     tooltip => "back to the starting page",
1542     on_activate => sub {
1543     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1544     unshift @future, @history;
1545     @history = ();
1546     $load_node->(@{shift @future});
1547     },
1548     );
1549    
1550 root 1.111 if (@history) {
1551 root 1.112 $buttons->add (new CFPlus::UI::Button
1552 root 1.113 text => "⋘",
1553 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1554 root 1.112 on_activate => sub {
1555 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1556     $load_node->(@{pop @history});
1557 root 1.113 },
1558     );
1559 root 1.112 }
1560 root 1.111
1561 root 1.112 if (@future) {
1562     $buttons->add (new CFPlus::UI::Button
1563 root 1.113 text => "â‹™",
1564 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1565 root 1.112 on_activate => sub {
1566 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1567     $load_node->(@{shift @future});
1568 root 1.113 },
1569     );
1570 root 1.111 }
1571 root 1.112
1572 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1573    
1574     my @path = CFPlus::Pod::full_path_of $node;
1575     pop @path; # drop current node
1576    
1577     for my $node (@path) {
1578     $buttons->add (new CFPlus::UI::Button
1579     text => $node->{kw}[0],
1580     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1581     on_activate => sub {
1582 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1583 root 1.113 $load_node->($node);
1584     },
1585     );
1586     $buttons->add (new CFPlus::UI::Label text => "/");
1587     }
1588    
1589 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1590 root 1.113
1591 root 1.112 $curnode = $node;
1592    
1593 root 1.111 $viewer->clear;
1594 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1595 root 1.115 $viewer->scroll_to ($para);
1596 root 1.111 };
1597    
1598     $load_node->(CFPlus::Pod::find pod => "mainpage");
1599    
1600 root 1.113 $CFPlus::Pod::goto_document = sub {
1601 root 1.111 my (@path) = @_;
1602    
1603 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1604 root 1.111
1605 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1606 root 1.111 $win->show;
1607     };
1608 root 1.1
1609     $win
1610     }
1611    
1612 root 1.119 sub open_string_query {
1613     my ($title, $cb, $txt, $tooltip) = @_;
1614     my $dialog = new CFPlus::UI::Toplevel
1615     x => "center",
1616     y => "center",
1617     z => 50,
1618     force_w => $WIDTH * 4/5,
1619     title => $title;
1620    
1621     $dialog->add (
1622     my $e = new CFPlus::UI::Entry
1623     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1624     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1625     tooltip => $tooltip
1626     );
1627    
1628     $e->grab_focus;
1629     $e->set_text ($txt) if $txt;
1630     $dialog->show;
1631     }
1632    
1633     sub open_quit_dialog {
1634     unless ($QUIT_DIALOG) {
1635     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1636     x => "center",
1637     y => "center",
1638     z => 50,
1639     title => "Really Quit?",
1640     on_key_down => sub {
1641     my ($dialog, $ev) = @_;
1642     $ev->{sym} == 27 and $dialog->hide;
1643     }
1644     ;
1645    
1646     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1647    
1648     $vb->add (new CFPlus::UI::Label
1649     text => "You should find a savebed and apply it first!",
1650     max_w => $WIDTH * 0.25,
1651     ellipsize => 0,
1652     );
1653     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1654     $hb->add (new CFPlus::UI::Button
1655     text => "Ok",
1656     expand => 1,
1657     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1658     );
1659     $hb->add (new CFPlus::UI::Button
1660     text => "Quit anyway",
1661     expand => 1,
1662     on_activate => sub { exit },
1663     );
1664     }
1665    
1666     $QUIT_DIALOG->show;
1667     $QUIT_DIALOG->grab_focus;
1668     }
1669    
1670     sub show_tip_of_the_day {
1671     # find all tips
1672     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1673    
1674 root 1.148 CFPlus::DB::get state => "tip_of_the_day", sub {
1675     my ($todindex) = @_;
1676     $todindex = 0 if $todindex >= @tod;
1677     CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
1678    
1679     # create dialog
1680     my $dialog;
1681    
1682     my $close = sub {
1683     $dialog->destroy;
1684     };
1685    
1686     $dialog = new CFPlus::UI::Toplevel
1687     x => "center",
1688     y => "center",
1689     z => 3,
1690     name => 'tip_of_the_day',
1691     force_w => int $WIDTH * 4/9,
1692     force_h => int $WIDTH * 2/9,
1693     title => "Tip of the day #" . (1 + $todindex),
1694     child => my $vbox = new CFPlus::UI::VBox,
1695     has_close_button => 1,
1696     on_delete => $close,
1697     ;
1698    
1699     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1700     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1701     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1702    
1703     $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
1704    
1705     $table->add (0, 0, new CFPlus::UI::Button
1706     text => "Close",
1707     tooltip => "Close the tip of the day window. To never see it again, disable the tip of the day in the <b>Server Setup</b>.",
1708     on_activate => $close,
1709     );
1710 root 1.119
1711 root 1.148 $table->add (2, 0, new CFPlus::UI::Button
1712     text => "Next",
1713     tooltip => "Show the next <b>Tip of the day</b>.",
1714     on_activate => sub {
1715     $close->();
1716     &show_tip_of_the_day;
1717     },
1718     );
1719 root 1.119
1720 root 1.148 $dialog->show;
1721 root 1.119 };
1722     }
1723    
1724 root 1.1 sub sdl_init {
1725 root 1.104 CFPlus::SDL_Init
1726 root 1.1 and die "SDL::Init failed!\n";
1727     }
1728    
1729     sub video_init {
1730     sdl_init;
1731    
1732     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1733    
1734     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1735    
1736     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1737     $FULLSCREEN = $CFG->{fullscreen};
1738     $FAST = $CFG->{fast};
1739    
1740 root 1.104 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1741     or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1742 root 1.1
1743     $SDL_ACTIVE = 1;
1744     $LAST_REFRESH = time - 0.01;
1745    
1746 root 1.104 CFPlus::OpenGL::init;
1747 root 1.1
1748     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1749    
1750 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1751 root 1.1
1752     #############################################################################
1753    
1754     if ($DEBUG_STATUS) {
1755 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1756 root 1.1 } else {
1757     # create the widgets
1758    
1759 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1760 root 1.30 padding => 0,
1761     z => 100,
1762     force_x => "max",
1763     force_y => 0;
1764 root 1.1 $DEBUG_STATUS->show;
1765 elmex 1.34
1766 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1767 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1768 root 1.1
1769 root 1.104 (new CFPlus::UI::Frame
1770 root 1.1 bg => [0, 0, 0, 0.4],
1771 root 1.30 force_x => 0,
1772     force_y => "max",
1773 root 1.1 child => $STATUSBOX,
1774     )->show;
1775    
1776 root 1.114 CFPlus::UI::Toplevel->new (
1777 root 1.47 title => "Map",
1778 root 1.42 name => "mapmap",
1779 root 1.30 x => 0,
1780     y => $FONTSIZE + 8,
1781 root 1.1 border_bg => [1, 1, 1, 192/255],
1782     bg => [1, 1, 1, 0],
1783 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1784 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1785     ),
1786     )->show;
1787    
1788 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1789 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1790     my ($mapwidget, $preset) = @_;
1791    
1792     if ($CONSOLE) {
1793     $CONSOLE->{input}->{auto_activated} = 1;
1794 root 1.74 $CONSOLE->{input}->grab_focus;
1795 root 1.1
1796     if ($preset && $CONSOLE->{input}->get_text eq '') {
1797     $CONSOLE->{input}->set_text ($preset);
1798     }
1799     }
1800     });
1801     $MAPWIDGET->show;
1802 root 1.74 $MAPWIDGET->grab_focus;
1803 root 1.1
1804 root 1.104 $LOGVIEW = new CFPlus::UI::TextScroller
1805 root 1.1 expand => 1,
1806     font => $FONT_FIXED,
1807     fontsize => $::CFG->{log_fontsize},
1808 root 1.61 indent => -4,
1809 root 1.1 can_hover => 1,
1810     can_events => 1,
1811 root 1.149 max_par => $CFG->{logview_max_par},
1812     tooltip => "<b>Server Log</b>. This text viewer contains all recent messages sent by the server.",
1813 root 1.1 ;
1814    
1815 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1816 root 1.49 title => "Setup",
1817     name => "setup_dialog",
1818     x => 'center',
1819     y => 'center',
1820 root 1.53 z => 2,
1821 root 1.49 force_w => $::WIDTH * 0.6,
1822     force_h => $::HEIGHT * 0.6,
1823 root 1.74 has_close_button => 1,
1824 root 1.49 ;
1825    
1826 elmex 1.81 $METASERVER = metaserver_dialog;
1827    
1828 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1829     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1830 root 1.49
1831     $SETUP_NOTEBOOK->add (Server => $SETUP_SERVER = server_setup,
1832     "Configure the server to play on, your username, password and other server-related options.");
1833 root 1.149 $SETUP_NOTEBOOK->add (Client => client_setup,
1834     "Configure various client-specific settings.");
1835 root 1.49 $SETUP_NOTEBOOK->add (Graphics => graphics_setup,
1836     "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1837     $SETUP_NOTEBOOK->add (Audio => audio_setup,
1838     "Configure the use of audio, sound effects and background music.");
1839     $SETUP_NOTEBOOK->add (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1840 root 1.75 "Lets you define, edit and delete key bindings."
1841     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1842 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1843 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1844 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1845     . "binding editor closes");
1846 root 1.65 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1847 root 1.75 "Some debuggin' options. Do not ask.");
1848 root 1.49
1849 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1850 root 1.1
1851 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1852 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1853    
1854 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1855 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1856    
1857     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
1858    
1859 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1860 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1861 root 1.1
1862 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1863 root 1.1 text => "Save Config",
1864     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1865 root 1.18 on_activate => sub {
1866 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1867     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1868 root 1.1 status "Configuration Saved";
1869 root 1.74 0
1870 root 1.1 },
1871     );
1872    
1873 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1874 root 1.1 tooltip => "View Documentation");
1875    
1876 elmex 1.134
1877 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1878 root 1.18 text => "Quit",
1879     tooltip => "Terminates the program",
1880     on_activate => sub {
1881 root 1.1 if ($CONN) {
1882     open_quit_dialog;
1883     } else {
1884     exit;
1885     }
1886 root 1.74 0
1887 root 1.1 },
1888     );
1889    
1890     $BUTTONBAR->show;
1891 root 1.49 $SETUP_DIALOG->show;
1892     }
1893 root 1.1
1894 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1895 root 1.1 }
1896    
1897 elmex 1.134 sub setup_build_button {
1898     my ($enabled) = @_;
1899     if ($enabled) {
1900 elmex 1.140 $BUILD_BUTTON->hide if $BUILD_BUTTON;
1901 elmex 1.134 $BUILD_BUTTON ||= new CFPlus::UI::Button
1902     text => "Build",
1903     tooltip => "Opens the ingame builder",
1904     on_activate => sub {
1905     if ($CONN) {
1906     $CONN->send_ext_req (builder_player_items => sub {
1907     open_ingame_editor ($_[0]) if exists $_[0]->{items};
1908     });
1909     }
1910     0
1911     };
1912     $BUTTONBAR->add ($BUILD_BUTTON);
1913     } else {
1914     $BUILD_BUTTON->hide if $BUILD_BUTTON;
1915     }
1916     }
1917    
1918     sub open_ingame_editor {
1919     my ($msg) = @_;
1920    
1921     my $win = new CFPlus::UI::Toplevel
1922     x => 0,
1923     y => 'center',
1924     z => 4,
1925     name => 'builder_window',
1926     force_w => int $WIDTH * 1/4,
1927     force_h => int $HEIGHT * 3/4,
1928     title => "In game builder",
1929     has_close_button => 1;
1930    
1931     my $r = new CFPlus::UI::ScrolledWindow (
1932     expand => 1,
1933     scroll_y => 1
1934     );
1935     $r->add (my $vb = new CFPlus::UI::VBox);
1936     $win->add ($r);
1937    
1938    
1939 elmex 1.135 $vb->add (
1940     new CFPlus::UI::Button
1941     text => "Disable build mode",
1942     on_activate => sub { $::IN_BUILD_MODE = undef }
1943     );
1944     $vb->add (
1945     new CFPlus::UI::Button
1946     text => "ERASE",
1947     on_activate => sub { $::IN_BUILD_MODE = { do_erase => 1 } }
1948     );
1949 elmex 1.134
1950 elmex 1.135 for my $itemarchname (
1951     sort {
1952     $msg->{items}->{$a}->{build_arch_name}
1953     cmp $msg->{items}->{$b}->{build_arch_name}
1954     } keys %{$msg->{items}}
1955     ) {
1956     my $info = $msg->{items}->{$itemarchname};
1957 elmex 1.134 $vb->add (
1958 elmex 1.135 new CFPlus::UI::Button text => $info->{build_arch_name},
1959 elmex 1.134 on_activate => sub {
1960 elmex 1.135 $::IN_BUILD_MODE = { item => $itemarchname, info => $info };
1961    
1962     if (grep { $msg->{items}->{$itemarchname}->{$_} } qw/has_connection has_name has_text/) {
1963     build_mode_query_arch_info ();
1964     }
1965 elmex 1.134 }
1966     );
1967     }
1968    
1969     $win->show;
1970     }
1971    
1972 elmex 1.135 sub build_mode_query_arch_info {
1973     my ($iteminfo) = $::IN_BUILD_MODE;
1974     my $itemarchname = $iteminfo->{item};
1975     my $info = $iteminfo->{info};
1976    
1977     my $dialog = new CFPlus::UI::Toplevel
1978     x => "center",
1979     y => "center",
1980     z => 50,
1981     force_w => int $WIDTH * 1/2,
1982     title => "Enter information for placement of '$itemarchname'",
1983     has_close_button => 1;
1984    
1985     $dialog->add (my $vb = new CFPlus::UI::VBox expand => 1);
1986    
1987     $vb->add (my $table = new CFPlus::UI::Table expand => 1);
1988     my $row = 0;
1989     if ($info->{has_name}) {
1990     $table->add (0, $row, new CFPlus::UI::Label text => "Name:");
1991     $table->add (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{name} = $_[1]; 0 });
1992     }
1993     if ($info->{has_text}) {
1994     $table->add (0, $row, new CFPlus::UI::Label text => "Text:");
1995     $table->add (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{text} = $_[1]; 0 });
1996     }
1997     if ($info->{has_connection}) {
1998     $table->add (0, $row, new CFPlus::UI::Label text => "Connection ID:");
1999     $table->add (1, $row++,
2000     new CFPlus::UI::Entry
2001     expand => 1,
2002     on_changed => sub { $::IN_BUILD_MODE->{connection} = $_[1]; 0 },
2003     tooltip => "Enter the connection ID here. The connection ID connects actors like a lever to a gate or a magic ear to a gate"
2004     );
2005     }
2006    
2007     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
2008     $hb->add (new CFPlus::UI::Button
2009     text => "Close",
2010     expand => 1,
2011     on_activate => sub { $dialog->hide; 0 },
2012     );
2013     $dialog->show;
2014     }
2015    
2016 root 1.1 sub video_shutdown {
2017 root 1.104 CFPlus::OpenGL::shutdown;
2018 root 1.73
2019 root 1.1 undef $SDL_ACTIVE;
2020     }
2021    
2022     sub audio_channel_finished {
2023     my ($channel) = @_;
2024    
2025     #warn "channel $channel finished\n";#d#
2026     }
2027    
2028 root 1.163 sub audio_music_set {
2029     my ($songs) = @_;
2030    
2031 root 1.168 my @want =
2032     grep $_,
2033     map $CONN->{music_meta}{$_},
2034     @$songs;
2035 root 1.163
2036 root 1.168 if (@want) {
2037     @MUSIC_WANT = @want;
2038     &audio_music_changed ();
2039     }
2040     }
2041    
2042     sub audio_music_start {
2043     my $path = $MUSIC_PLAYING->{path}
2044     or return;
2045    
2046     CFPlus::DB::prefetch_file $path, 1024_000, sub {
2047     # music might have changed...
2048     $path eq $MUSIC_PLAYING->{path}
2049     or return &audio_music_start ();
2050    
2051     $MUSIC_PLAYER = new_from_file CFPlus::MixMusic $path;
2052    
2053     my $NOW = time;
2054    
2055     if ($MUSIC_PLAYING->{stop_time} > $NOW - $MUSIC_RESUME) {
2056     my $pos = $MUSIC_PLAYING->{stop_pos};
2057     $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
2058     $MUSIC_START = time - $pos;
2059     } else {
2060     $MUSIC_PLAYER->play (0);
2061     $MUSIC_START = time;
2062     }
2063 root 1.163
2064 root 1.168 delete $MUSIC_PLAYING->{stop_time};
2065     delete $MUSIC_PLAYING->{stop_pos};
2066 root 1.163 }
2067     }
2068    
2069     sub audio_music_changed {
2070 root 1.1 return unless $CFG->{bgm_enable};
2071    
2072 root 1.165 # default MUSIC_WANT == MUSIC_DEFAULT
2073 root 1.163 @MUSIC_WANT = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_WANT;
2074    
2075     # if the currently playing song is acceptable, let it continue
2076 root 1.164 return if $MUSIC_PLAYING
2077     && grep $MUSIC_PLAYING->{path} eq $_->{path}, @MUSIC_WANT;
2078 root 1.163
2079 root 1.168 my $NOW = time;
2080    
2081 root 1.164 if ($MUSIC_PLAYING) {
2082 root 1.168 $MUSIC_PLAYING->{stop_time} = $NOW;
2083     $MUSIC_PLAYING->{stop_pos} = $NOW - $MUSIC_START;
2084 root 1.165 CFPlus::MixMusic::fade_out 1000;
2085 root 1.163 } else {
2086 root 1.168 # sort by stop time, oldest first
2087     @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
2088 root 1.163
2089 root 1.168 # if the most recently-played piece played very recently,
2090     # resume it, else choose the oldest piece for rotation.
2091     $MUSIC_PLAYING =
2092     $MUSIC_WANT[-1]{stop_time} > $NOW - $MUSIC_RESUME
2093     ? $MUSIC_WANT[-1]
2094     : $MUSIC_WANT[0];
2095 root 1.163
2096 root 1.168 audio_music_start;
2097 root 1.163 }
2098     }
2099    
2100     sub audio_music_finished {
2101     $MUSIC_PLAYING = undef;
2102     undef $MUSIC_PLAYER;
2103 root 1.1
2104 root 1.163 audio_music_changed;
2105 root 1.1 }
2106    
2107     sub audio_init {
2108     if ($CFG->{audio_enable}) {
2109 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
2110     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
2111 root 1.1
2112     unless ($SDL_MIXER) {
2113     status "Unable to open sound device: there will be no sound";
2114     return;
2115     }
2116    
2117 root 1.104 CFPlus::Mix_AllocateChannels 8;
2118     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
2119 root 1.1
2120     audio_music_finished;
2121    
2122 root 1.129 local $_;
2123 root 1.1 while (<$fh>) {
2124     next if /^\s*#/;
2125     next if /^\s*$/;
2126    
2127     my ($file, $volume, $event) = split /\s+/, $_, 3;
2128    
2129     push @SOUNDS, "$volume,$file";
2130    
2131 root 1.129 $AUDIO_CHUNKS{"$volume,$file"} ||= do {
2132     my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
2133     $chunk->volume ($volume * 128 / 100);
2134     $chunk
2135     };
2136 root 1.1 }
2137     } else {
2138     status "unable to open sound config: $!";
2139     }
2140     }
2141     }
2142    
2143     sub audio_shutdown {
2144 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
2145 root 1.1 undef $SDL_MIXER;
2146     @SOUNDS = ();
2147     %AUDIO_CHUNKS = ();
2148     }
2149    
2150     my %animate_object;
2151     my $animate_timer;
2152    
2153     my $fps = 9;
2154    
2155     my %demo;#d#
2156    
2157     sub force_refresh {
2158     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
2159 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
2160 root 1.1
2161 root 1.104 $CFPlus::UI::ROOT->draw;
2162 root 1.1
2163     $WANT_REFRESH = 0;
2164     $CAN_REFRESH = 0;
2165     $LAST_REFRESH = $NOW;
2166    
2167 root 1.104 CFPlus::SDL_GL_SwapBuffers;
2168 root 1.1 }
2169    
2170 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
2171 root 1.1 $NOW = time;
2172    
2173     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2174 root 1.169 for CFPlus::poll_events;
2175 root 1.1
2176     if (%animate_object) {
2177     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2178     $WANT_REFRESH++;
2179     }
2180    
2181     if ($WANT_REFRESH) {
2182     force_refresh;
2183     } else {
2184     $CAN_REFRESH = 1;
2185     }
2186     });
2187    
2188     sub animation_start {
2189     my ($widget) = @_;
2190     $animate_object{$widget} = $widget;
2191     }
2192    
2193     sub animation_stop {
2194     my ($widget) = @_;
2195     delete $animate_object{$widget};
2196     }
2197    
2198     # check once/second for faces that need to be prefetched
2199     # this should, of course, only run on demand, but
2200     # SDL forces worse things on us....
2201    
2202     Event->timer (after => 1, interval => 0.25, cb => sub {
2203     $CONN->face_prefetch
2204     if $CONN;
2205     });
2206    
2207     %SDL_CB = (
2208 root 1.104 CFPlus::SDL_QUIT => sub {
2209 root 1.151 exit;
2210 root 1.1 },
2211 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2212 root 1.1 },
2213 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2214     CFPlus::UI::full_refresh;
2215 root 1.1 },
2216 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2217 root 1.137 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2218 root 1.171 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2219     # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2220 root 1.137 # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2221     # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2222     # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2223 root 1.1 },
2224 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2225     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2226 root 1.1 # alt-enter
2227 root 1.94 $FULLSCREEN_ENABLE->toggle;
2228 root 1.1 video_shutdown;
2229     video_init;
2230     } else {
2231 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2232 root 1.1 }
2233     },
2234 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2235     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2236     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2237     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2238     CFPlus::SDL_USEREVENT => sub {
2239 root 1.1 if ($_[0]{code} == 1) {
2240     audio_channel_finished $_[0]{data1};
2241     } elsif ($_[0]{code} == 0) {
2242     audio_music_finished;
2243     }
2244     },
2245     );
2246    
2247     #############################################################################
2248    
2249     $SIG{INT} = $SIG{TERM} = sub { exit };
2250    
2251     {
2252 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2253 root 1.147 CFPlus::DB::Server::run;
2254    
2255 root 1.104 CFPlus::UI::set_layout ($::CFG->{layout});
2256 root 1.1
2257     my %DEF_CFG = (
2258 root 1.149 sdl_mode => 0,
2259     width => 640,
2260     height => 480,
2261     fullscreen => 0,
2262     fast => 0,
2263     map_scale => 1,
2264     fow_enable => 1,
2265     fow_intensity => 0.45,
2266     fow_smooth => 0,
2267     map_smoothing => 1,
2268     gui_fontsize => 1,
2269     log_fontsize => 0.7,
2270     gauge_fontsize => 1,
2271     gauge_size => 0.35,
2272     stat_fontsize => 0.7,
2273     mapsize => 100,
2274     say_command => 'chat',
2275     audio_enable => 1,
2276     bgm_enable => 1,
2277     bgm_volume => 0.25,
2278     face_prefetch => 0,
2279     output_sync => 1,
2280     output_count => 1,
2281     output_rate => "",
2282     pickup => 0,
2283     inv_sort => "mtime",
2284     default => "profile", # default profile
2285     show_tips => 1,
2286     logview_max_par => 1000,
2287 root 1.1 );
2288 root 1.75
2289 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2290     $CFG->{$k} = $v unless exists $CFG->{$k};
2291     }
2292    
2293 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2294 root 1.137 $PROFILE = $CFG->{profile}{default};
2295    
2296     # convert old bindings (only default profile matters)
2297     if (my $bindings = delete $PROFILE->{bindings}) {
2298     while (my ($mod, $syms) = each %$bindings) {
2299     while (my ($sym, $cmds) = each %$syms) {
2300     push @{ $PROFILE->{macro} }, {
2301     accelkey => [$mod*1, $sym*1],
2302     action => $cmds,
2303     };
2304     }
2305     }
2306     }
2307 root 1.75
2308 root 1.1 sdl_init;
2309    
2310     @SDL_MODES = reverse
2311     grep $_->[0] >= 640 && $_->[1] >= 480,
2312 root 1.104 CFPlus::SDL_ListModes;
2313 root 1.1
2314 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2315 root 1.1
2316     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2317    
2318     {
2319 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2320 root 1.1 DejaVuSans.ttf
2321     DejaVuSansMono.ttf
2322     DejaVuSans-Bold.ttf
2323     DejaVuSansMono-Bold.ttf
2324     DejaVuSans-Oblique.ttf
2325     DejaVuSansMono-Oblique.ttf
2326     DejaVuSans-BoldOblique.ttf
2327     DejaVuSansMono-BoldOblique.ttf
2328     );
2329    
2330 root 1.104 CFPlus::add_font $_ for @fonts;
2331 root 1.1
2332 root 1.104 CFPlus::pango_init;
2333 root 1.1
2334 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2335     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2336 root 1.1
2337     $FONT_PROP->make_default;
2338     }
2339    
2340     # compare mono (ft) vs. rgba (cairo)
2341     # ft - 1.8s, cairo 3s, even in alpha-only mode
2342     # for my $rgba (0..1) {
2343     # my $t1 = Time::HiRes::time;
2344     # for (1..1000) {
2345 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2346 root 1.1 # $layout->set_text ("hallo" x 100);
2347     # $layout->render;
2348     # }
2349     # my $t2 = Time::HiRes::time;
2350     # warn $t2-$t1;
2351     # }
2352    
2353 root 1.122 $startup_done->();
2354    
2355 root 1.1 video_init;
2356     audio_init;
2357     }
2358    
2359 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2360    
2361 root 1.1 Event::loop;
2362 root 1.104 #CFPlus::SDL_Quit;
2363     #CFPlus::_exit 0;
2364 root 1.1
2365 root 1.150 END {
2366 root 1.151 CFPlus::SDL_Quit;
2367 root 1.150 CFPlus::DB::Server::stop;
2368     }
2369 root 1.1
2370     =head1 NAME
2371    
2372 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2373 root 1.1
2374     =head1 SYNOPSIS
2375    
2376     Just run it - no commandline arguments are supported.
2377    
2378     =head1 USAGE
2379    
2380 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2381 root 1.1 fullscreen and interactively.
2382    
2383 root 1.39 =head1 DEBUGGING
2384    
2385    
2386     CFPLUS_DEBUG - environment variable
2387    
2388     1 draw borders around widgets
2389     2 add low-level widget info to tooltips
2390     4 show fps
2391     8 suppress tooltips
2392    
2393 root 1.1 =head1 AUTHOR
2394    
2395     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2396    
2397    
2398