ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.177
Committed: Thu Jul 19 15:43:14 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.176: +2 -0 lines
Log Message:
sort modes by size againĀ§

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