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

File Contents

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