ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.178
Committed: Fri Jul 20 16:32:04 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.177: +94 -93 lines
Log Message:
change Table->add to add_at method and deprecate add method (to be replaced by a container-compliant one)

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