ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.168
Committed: Sat Jul 14 12:05:53 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.167: +48 -22 lines
Log Message:
greatly enhance and improve music selection algorithm and reduce database load

File Contents

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