ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.173
Committed: Tue Jul 17 17:39:08 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.172: +4 -17 lines
Log Message:
- implement ap smoothing in software (no longer relying
  on opengl 1.2, or, more to the point, cirrect/non-ati
  implementations of opengl 1.2)
- use a much harder smoothing matrix
- default fow intensity to 0
- just ignore events for mysteriously destroyed widgets
  (probably left-over event in queue?)

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