ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.172
Committed: Tue Jul 17 16:02:14 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.171: +15 -11 lines
Log Message:
support non-32bpp displays and non-alpha displays with lower quality (hopefully)

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