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

# 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_at (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_at (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 unshift @dialog, new CFPlus::UI::Label
331 max_w => $::WIDTH * 0.4,
332 ellipsise => 0,
333 markup => "\nOr use your keyboard and the text entry below:\n";
334
335 unshift @dialog, my $table = new CFPlus::UI::Table;
336
337 # left: re-roll
338 $table->add_at (0, 0, new CFPlus::UI::Button
339 text => "Roll Again",
340 on_activate => sub {
341 $conn->send ("reply y");
342 destroy_query_dialog $conn;
343 0
344 },
345 );
346
347 # center: swap stats
348 my ($sw1, $sw2) = map +(new CFPlus::UI::Selector
349 expand => 1,
350 value => $_,
351 options => [
352 [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 ],
360 ), 1 .. 2;
361
362 $table->add_at (2, 0, new CFPlus::UI::Button
363 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 0
369 },
370 );
371 $table->add_at (2, 1, new CFPlus::UI::HBox children => [$sw1, $sw2]);
372
373 # right: accept
374 $table->add_at (4, 0, new CFPlus::UI::Button
375 text => "Accept",
376 on_activate => sub {
377 $conn->send ("reply n");
378 $STATS_PAGE->hide;
379 destroy_query_dialog $conn;
380 0
381 },
382 );
383
384 unshift @dialog, my $hbox = new CFPlus::UI::HBox;
385 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 $hbox->add (new CFPlus::UI::Label
396 markup => "$conn->{stat}{$id} <span foreground='yellow'>$name</span>",
397 align => 0,
398 expand => 1,
399 can_events => 1,
400 can_hover => 1,
401 tooltip => "#stat_$name",
402 );
403 }
404
405 unshift @dialog, new CFPlus::UI::Label
406 max_w => $::WIDTH * 0.4,
407 ellipsise => 0,
408 markup => (CFPlus::Pod::section_label ui => "chargen_stats"),
409 ;
410 }
411
412 push @dialog, my $entry = new CFPlus::UI::Entry
413 on_changed => sub {
414 $conn->send ("reply $_[1]");
415 destroy_query_dialog $conn;
416 0
417 },
418 ;
419
420 $entry->grab_focus;
421
422 } else {
423 $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
424
425 push @dialog, my $entry = new CFPlus::UI::Entry
426 $flags & CS_QUERY_HIDEINPUT ? (hidden => "*") : (),
427 on_activate => sub {
428 $conn->send ("reply $_[1]");
429 destroy_query_dialog $conn;
430 0
431 },
432 ;
433
434 $entry->grab_focus;
435 }
436
437 $vbox->add (@dialog);
438 $dialog->show;
439 }
440
441 sub start_game {
442 status "logging in...";
443
444 $LOGIN_BUTTON->set_text ("Logout");
445 $SETUP_DIALOG->hide;
446
447 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
448
449 my ($host, $port) = split /:/, $PROFILE->{host};
450
451 $MAP = new CFPlus::Map;
452
453 $CONN = eval {
454 new CFPlus::Protocol
455 host => $host,
456 port => $port || 13327,
457 user => $PROFILE->{user},
458 pass => $PROFILE->{password},
459 mapw => $mapsize,
460 maph => $mapsize,
461
462 client => "cfplus $CFPlus::VERSION $] $^O",
463
464 map_widget => $MAPWIDGET,
465 logview => $LOGVIEW,
466 statusbox => $STATUSBOX,
467 map => $MAP,
468 mapmap => $MAPMAP,
469 query => \&server_query,
470
471 setup_req => {
472 smoothing => $CFG->{map_smoothing}*1,
473 },
474
475 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 };
487
488 if ($CONN) {
489 CFPlus::lowdelay fileno $CONN->{fh};
490
491 status "login successful";
492 } else {
493 status "unable to connect";
494 stop_game();
495 }
496 }
497
498 sub stop_game {
499 $LOGIN_BUTTON->set_text ("Login");
500 $SETUP_NOTEBOOK->set_current_page ($SETUP_SERVER);
501 $SETUP_DIALOG->show;
502 $PL_WINDOW->hide;
503 $SPELL_LIST->clear_spells;
504 $CFPlus::UI::ROOT->emit (stop_game => ! ! $CONN);
505
506 &audio_music_set ([]);
507
508 return unless $CONN;
509
510 status "connection closed";
511
512 destroy_query_dialog $CONN;
513 $CONN->destroy;
514 $CONN = 0; # false, does not autovivify
515
516 undef $MAP;
517 }
518
519 sub graphics_setup {
520 my $vbox = new CFPlus::UI::VBox;
521
522 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
523
524 my $row = 0;
525
526 $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 can_events => 1,
529 tooltip => "<tt><span size='8192'>" . (CFPlus::OpenGL::gl_extensions) . "</span></tt>");
530
531 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 $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
538 $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
545 $mode_slider->connect (changed => sub {
546 my ($self, $value) = @_;
547
548 $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
549 $mode_label->set_text (sprintf '%dx%d@%d+%d', @{$SDL_MODES[$value]});
550 });
551 $mode_slider->emit (changed => $mode_slider->{range}[0]);
552
553 $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 state => $CFG->{fullscreen},
556 tooltip => "Bring the client into fullscreen mode.",
557 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
558 );
559
560 $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 state => $CFG->{fast},
563 tooltip => "Lower the visual quality considerably to speed up rendering.",
564 on_changed => sub { my ($self, $value) = @_; $CFG->{fast} = $value; 0 }
565 );
566
567 $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 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
570 tooltip => "The base font size used by most GUI elements that do not have their own setting.",
571 on_changed => sub { $CFG->{gui_fontsize} = $_[1]; 0 },
572 );
573
574 $table->add_at (1, $row++, new CFPlus::UI::Button
575 expand => 1, align => 0, text => "Apply",
576 tooltip => "Apply the video settings above.",
577 on_activate => sub {
578 video_shutdown ();
579 video_init ();
580 0
581 }
582 );
583
584 $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 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
587 tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
588 on_changed => sub { my ($self, $value) = @_; $CFG->{map_scale} = 2 ** $value; 0 }
589 );
590
591 $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 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 $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 state => $CFG->{fow_enable},
603 tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
604 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_enable} = $value; 0 }
605 );
606
607 $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 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 on_changed => sub { my ($self, $value) = @_; $CFG->{fow_intensity} = $value; 0 }
612 );
613
614 $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 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 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
619 );
620
621 $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 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 on_changed => sub {
626 $CFG->{gauge_fontsize} = $_[1];
627 &set_gauge_window_fontsize;
628 0
629 }
630 );
631
632 $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 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 $CFG->{gauge_size} = $_[1];
638 $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
639 0
640 }
641 );
642
643 $vbox
644 }
645
646 sub audio_setup {
647 my $vbox = new CFPlus::UI::VBox;
648
649 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]);
650
651 my $row = 0;
652
653 $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 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 on_changed => sub { $CFG->{audio_enable} = $_[1]; 0 }
658 );
659 # $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 # $CFG->{effects_volume} = $_[1];
662 # });
663 $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 $hbox->add (new CFPlus::UI::CheckBox
666 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 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 }
669 );
670 $hbox->add (new CFPlus::UI::Slider
671 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
672 tooltip => "The volume of the background music. Changes are instant.",
673 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFPlus::MixMusic::volume $_[1] * 128; 0 }
674 );
675
676 $table->add_at (1, $row++, new CFPlus::UI::Button
677 expand => 1, align => 0, text => "Apply",
678 tooltip => "Apply the audio settings",
679 on_activate => sub {
680 audio_shutdown ();
681 audio_init ();
682 0
683 }
684 );
685
686 $vbox
687 }
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 my $win = new CFPlus::UI::Frame (
699 force_x => 0,
700 force_y => "max",
701 force_w => $WIDTH,
702 force_h => $gh,
703 );
704
705 $win->add (my $hbox = new CFPlus::UI::HBox
706 children => [
707 (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 ]),
712 (my $vbox = new CFPlus::UI::VBox),
713 ],
714 );
715
716 $vbox->add (new CFPlus::UI::HBox
717 expand => 1,
718 children => [
719 (new CFPlus::UI::Empty expand => 1),
720 (my $hb = new CFPlus::UI::HBox),
721 ],
722 );
723
724 $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
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 sub debug_setup {
743 my $table = new CFPlus::UI::Table;
744
745 $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
755 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
756
757 $table
758 }
759
760 sub stats_window {
761 my $r = new CFPlus::UI::ScrolledWindow (
762 expand => 1,
763 scroll_y => 1
764 );
765 $r->add (my $vb = new CFPlus::UI::VBox);
766
767 $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 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 $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
776 can_hover => 1, can_events => 1,
777 tooltip => "The map you are currently on (if supported by the server).");
778
779 $pi->add (my $hb0 = new CFPlus::UI::HBox);
780 $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
781 can_hover => 1, can_events => 1,
782 tooltip => "The weight of the player including all inventory items.");
783 $hb0->add ($STATWIDS->{m_weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
784 can_hover => 1, can_events => 1,
785 tooltip => "The weight limit: you cannot carry more than this.");
786
787 $vb->add (new CFPlus::UI::FancyFrame
788 label => "Primary/Secondary Statistics",
789 child => (my $hb = new CFPlus::UI::HBox expand => 1),
790 );
791 $hb->add (my $tbl = new CFPlus::UI::Table expand => 1);
792
793 my $color2 = [1, 1, 0];
794
795 for (
796 [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 ) {
811 my ($col, $row, $id, $label, $template) = @$_;
812
813 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label
814 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0,
815 align => +1, template => $template, tooltip => "#stat_$label");
816 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label
817 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0,
818 align => -1, text => $label, tooltip => "#stat_$label");
819 }
820
821 $vb->add (new CFPlus::UI::FancyFrame
822 label => "Resistancies",
823 child => (my $tbl2 = new CFPlus::UI::Table expand => 1),
824 );
825
826 my $row = 0;
827 my $col = 0;
828
829 my %resist_names = (
830 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 );
867
868 for (qw/slow holyw conf fire depl magic
869 drain acid pois para deat phys
870 blind fear tund elec cold ghit/)
871 {
872 $tbl2->add_at ($col, $row,
873 $STATWIDS->{"res_$_"} =
874 new CFPlus::UI::Label
875 font => $FONT_FIXED,
876 template => "-100%",
877 align => +1,
878 valign => 0,
879 can_events => 1,
880 can_hover => 1,
881 tooltip => $resist_names{$_}->[1],
882 );
883 $tbl2->add_at ($col + 1, $row, new CFPlus::UI::Image
884 font => $FONT_FIXED,
885 can_hover => 1,
886 can_events => 1,
887 path => "ui/resist/resist_$_.png",
888 tooltip => $resist_names{$_}->[1],
889 );
890 $tbl2->add_at ($col + 2, $row, new CFPlus::UI::Label
891 text => $resist_names{$_}->[0],
892 font => $FONT_FIXED,
893 can_hover => 1,
894 can_events => 1,
895 tooltip => $resist_names{$_}->[1],
896 );
897
898 $row++;
899 if ($row % 6 == 0) {
900 $col += 3;
901 $row = 0;
902 }
903 }
904
905 #update_stats_window ({});
906
907 $r
908 }
909
910 sub skill_window {
911 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 $sw
914 }
915
916 sub formsep($) {
917 scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
918 }
919
920 my $METASERVER_ATIME;
921
922 sub update_metaserver {
923 my ($metaserver_dialog) = @_;
924
925 $METASERVER = $metaserver_dialog
926 if defined $metaserver_dialog;
927
928 return if $METASERVER_ATIME > time;
929 $METASERVER_ATIME = time + 60;
930
931 my $table = $METASERVER->{table};
932 $table->clear;
933 $table->add_at (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
934
935 my $ok = 0;
936
937 CFPlus::background {
938 my $ua = CFPlus::lwp_useragent;
939
940 CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content;
941 } sub {
942 my ($msg) = @_;
943 if ($msg) {
944 $table->clear;
945
946 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 $table->add_at ($_, 0, new CFPlus::UI::Label
955 can_hover => 1, can_events => 1,
956 align => 0, fg => [1, 1, 0],
957 text => $col[$_], tooltip => $tip[$_])
958 for 0 .. $#col;
959
960 my @align = qw(1 0 1 1 -1);
961
962 my $y = 0;
963 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
967 for ($desc) {
968 s/<br>/\n/gi;
969 s/<li>/\n· /gi;
970 s/<.*?>//sgi;
971 s/&amp;/&/g;
972 s/&lt;/</g;
973 s/&gt;/>/g;
974 }
975
976 $uptime = sprintf "%dd %02d:%02d:%02d",
977 (int $uptime / 86400),
978 (int $uptime / 3600) % 24,
979 (int $uptime / 60) % 60,
980 $uptime % 60;
981
982 $m = [$users, $host, $uptime, $version, $desc];
983
984 $y++;
985
986 $table->add_at (scalar @$m, $y, new CFPlus::UI::VBox children => [
987 (new CFPlus::UI::Button
988 text => "Use",
989 tooltip => "Put this server into the <b>Host:Port</b> field",
990 on_activate => sub {
991 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
992 $METASERVER->hide;
993 0
994 },
995 ),
996 (new CFPlus::UI::Empty expand => 1),
997 ]);
998
999 $table->add_at ($_, $y, new CFPlus::UI::Label
1000 max_w => $::WIDTH * 0.4,
1001 ellipsise => 0,
1002 align => $align[$_],
1003 text => $m->[$_],
1004 tooltip => $tip[$_],
1005 fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1006 can_hover => 1,
1007 can_events => 1,
1008 fontsize => 0.8)
1009 for 0 .. $#$m;
1010 }
1011 } else {
1012 $ok or $label->set_text ("error while contacting metaserver");
1013 }
1014 };
1015
1016 }
1017
1018 sub metaserver_dialog {
1019 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
1023 my $dialog = new CFPlus::UI::Toplevel
1024 title => "Server List",
1025 name => 'metaserver_dialog',
1026 x => 'center',
1027 y => 'center',
1028 z => 3,
1029 force_w => $::WIDTH * 0.9,
1030 force_h => $::HEIGHT * 0.7,
1031 child => $vbox,
1032 has_close_button => 1,
1033 table => $table,
1034 on_visibility_change => sub {
1035 update_metaserver ($_[0]) if $_[1];
1036 0
1037 },
1038 ;
1039
1040 $dialog
1041 }
1042
1043 sub server_setup {
1044 my $vbox = new CFPlus::UI::VBox;
1045
1046 $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 $table->add_at (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
1051
1052 {
1053 $table->add_at (1, 2, my $vbox = new CFPlus::UI::VBox);
1054
1055 $vbox->add (
1056 $HOST_ENTRY = new CFPlus::UI::Entry
1057 expand => 1,
1058 text => $CFG->{profile}{default}{host},
1059 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1060 on_changed => sub {
1061 my ($self, $value) = @_;
1062 $CFG->{profile}{default}{host} = $value;
1063 0
1064 }
1065 );
1066
1067 $vbox->add (new CFPlus::UI::Button
1068 expand => 1,
1069 text => "Server List",
1070 other => $METASERVER,
1071 tooltip => "Show a list of available crossfire servers",
1072 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1073 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 },
1074 );
1075 }
1076
1077 $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 text => $CFG->{profile}{default}{user},
1080 tooltip => "The name of your character on the server",
1081 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value }
1082 );
1083
1084 $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 text => $CFG->{profile}{default}{password},
1087 hidden => 1,
1088 tooltip => "The password for your character",
1089 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value }
1090 );
1091
1092 $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 force_w => 100,
1095 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 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 },
1101 );
1102
1103 $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 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 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
1114 );
1115
1116 $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 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 $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 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 on_changed => sub { $CFG->{output_count} = $_[1]; 0 },
1130 );
1131
1132 $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 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 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 },
1137 );
1138
1139 $table->add_at (1, 12, $LOGIN_BUTTON = new CFPlus::UI::Button
1140 expand => 1,
1141 align => 0,
1142 text => "Login",
1143 on_activate => sub {
1144 $CONN ? stop_game
1145 : start_game;
1146 0
1147 },
1148 );
1149
1150 $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 $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 text => $CFG->{say_command},
1166 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 . "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 on_changed => sub {
1170 my ($self, $value) = @_;
1171 $CFG->{say_command} = $value;
1172 0
1173 }
1174 );
1175
1176 $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 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 $CFG->{show_tips} = $value;
1183 0
1184 }
1185 );
1186
1187 $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 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 );
1199
1200 $table
1201 }
1202
1203 sub message_window {
1204 my $window = new CFPlus::UI::Toplevel
1205 name => "message_window",
1206 title => "Messages",
1207 border_bg => [1, 1, 1, 1],
1208 x => "max",
1209 y => 0,
1210 force_w => $::WIDTH * 0.4,
1211 force_h => $::HEIGHT * 0.5,
1212 child => (my $vbox = new CFPlus::UI::VBox),
1213 has_close_button => 1;
1214
1215 $vbox->add ($LOGVIEW);
1216
1217 $vbox->add (my $input = new CFPlus::UI::Entry
1218 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 on_focus_in => sub {
1223 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
1232 0
1233 },
1234 on_activate => sub {
1235 my ($input, $text) = @_;
1236 $input->set_text ('');
1237
1238 if ($text =~ /^\/(.*)/) {
1239 $::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
1249 0
1250 },
1251 on_escape => sub {
1252 $MAPWIDGET->grab_focus;
1253
1254 0
1255 },
1256 );
1257
1258 $CONSOLE = {
1259 window => $window,
1260 input => $input,
1261 };
1262
1263 $window
1264 }
1265
1266 sub autopickup_setup {
1267 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
1276 for (
1277 ["General", 0, 0,
1278 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1279 ["Inhibit autopickup" => PICKUP_INHIBIT],
1280 ["Stop before pickup" => PICKUP_STOP],
1281 ["Debug autopickup" => PICKUP_DEBUG],
1282 ],
1283 ["Weapons", 0, 6,
1284 ["All weapons" => PICKUP_ALLWEAPON],
1285 ["Missile weapons" => PICKUP_MISSILEWEAPON],
1286 ["Bows" => PICKUP_BOW],
1287 ["Arrows" => PICKUP_ARROW],
1288 ],
1289 ["Armour", 0, 12,
1290 ["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 ],
1297
1298 ["Readables", 2, 0,
1299 ["Spellbooks" => PICKUP_SPELLBOOK],
1300 ["Skillscrolls" => PICKUP_SKILLSCROLL],
1301 ["Normal Books/Scrolls" => PICKUP_READABLES],
1302 ],
1303 ["Misc", 2, 5,
1304 ["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 ["Flesh" => PICKUP_FLESH],
1314 ],
1315 ["Weight/Value ratio", 2, 17]
1316 )
1317 {
1318 my ($title, $x, $y, @bits) = @$_;
1319 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1320
1321 for (@bits) {
1322 ++$y;
1323
1324 my $mask = $_->[1];
1325 $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 state => $::CFG->{pickup} & $mask,
1328 on_changed => sub {
1329 my ($box, $value) = @_;
1330
1331 if ($value) {
1332 $::CFG->{pickup} |= $mask;
1333 } else {
1334 $::CFG->{pickup} &= ~$mask;
1335 }
1336
1337 $::CONN->send_command ("pickup $::CFG->{pickup}")
1338 if defined $::CONN;
1339
1340 0
1341 });
1342
1343 ${$_->[2]} = $checkbox if $_->[2];
1344 }
1345 }
1346
1347 $table->add_at (2, 18, new CFPlus::UI::ValSlider
1348 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1349 template => ">= 99",
1350 to_value => sub { ">= " . 5 * $_[0] },
1351 on_changed => sub {
1352 my ($slider, $value) = @_;
1353
1354 $::CFG->{pickup} &= ~0xF;
1355 $::CFG->{pickup} |= int $value
1356 if $value;
1357 1;
1358 });
1359
1360 $table->add_at (3, 18, new CFPlus::UI::Button
1361 text => "set",
1362 on_activate => sub {
1363 $::CONN->send_command ("pickup $::CFG->{pickup}")
1364 if defined $::CONN;
1365 0
1366 });
1367
1368 $r
1369 }
1370
1371 my %SORT_ORDER = (
1372 type => undef,
1373 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 weight => sub { sort {
1385 $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1386 or $a->{type} <=> $b->{type}
1387 } @_ },
1388 );
1389
1390 sub inventory_widget {
1391 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1392
1393 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1394 $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1395
1396 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1397
1398 use sort 'stable';
1399
1400 $hb1->add (new CFPlus::UI::Selector
1401 value => $::CFG->{inv_sort},
1402 options => [
1403 [type => "Type/Name"],
1404 [mtime => "Recent/Normal/Locked"],
1405 [weight => "Weight/Type"],
1406 ],
1407 on_changed => sub {
1408 $::CFG->{inv_sort} = $_[1];
1409 $INV->set_sort_order ($SORT_ORDER{$_[1]});
1410 },
1411 );
1412 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1413 #TODO# update to weigh/maxweight
1414 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1415
1416 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1417 $sw1->add ($INV = new CFPlus::UI::Inventory);
1418 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1419
1420 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1421
1422 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1423
1424 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1425 $sw2->add ($INVR = new CFPlus::UI::Inventory);
1426
1427 # XXX: Call after $INVR = ... because set_opencont sets the items
1428 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1429
1430 $hb
1431 }
1432
1433 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 sub player_window {
1445 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1446 x => "center",
1447 y => "center",
1448 force_w => $WIDTH * 9/10,
1449 force_h => $HEIGHT * 9/10,
1450 title => "Player",
1451 name => "playerbook",
1452 has_close_button => 1
1453 ;
1454
1455 my $ntb =
1456 $PL_NOTEBOOK =
1457 new CFPlus::UI::Notebook expand => 1;
1458
1459 $ntb->add (
1460 "Statistics (F2)" => $STATS_PAGE = stats_window,
1461 "Shows statistics, where all your Stats and Resistances are shown."
1462 );
1463 $ntb->add (
1464 "Skills (F3)" => $SKILL_PAGE = skill_window,
1465 "Shows all your Skills."
1466 );
1467
1468 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1469 $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1470 $ntb->add (
1471 "Spellbook (F4)" => $spellsw,
1472 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1473 );
1474 $ntb->add (
1475 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1476 "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 );
1479 $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
1482 $ntb->set_current_page ($INVENTORY_PAGE);
1483
1484 $plwin->add ($ntb);
1485 $plwin
1486 }
1487
1488 sub keyboard_setup {
1489 CFPlus::Macro::keyboard_setup
1490 }
1491
1492 sub help_window {
1493 my $win = new CFPlus::UI::Toplevel
1494 x => 'center',
1495 y => 'center',
1496 z => 4,
1497 name => 'doc_browser',
1498 force_w => int $WIDTH * 7/8,
1499 force_h => int $HEIGHT * 7/8,
1500 title => "Help Browser",
1501 has_close_button => 1;
1502
1503 $win->add (my $vbox = new CFPlus::UI::VBox);
1504
1505 $vbox->add (new CFPlus::UI::FancyFrame
1506 label => "Navigation",
1507 child => (my $buttons = new CFPlus::UI::HBox),
1508 );
1509 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1510 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1511
1512 my @history;
1513 my @future;
1514 my $curnode;
1515
1516 my $load_node; $load_node = sub {
1517 my ($node, $para) = @_;
1518
1519 $buttons->clear;
1520
1521 $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 if (@history) {
1533 $buttons->add (new CFPlus::UI::Button
1534 text => "⋘",
1535 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1536 on_activate => sub {
1537 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1538 $load_node->(@{pop @history});
1539 },
1540 );
1541 }
1542
1543 if (@future) {
1544 $buttons->add (new CFPlus::UI::Button
1545 text => "â‹™",
1546 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1547 on_activate => sub {
1548 push @history, [$curnode, $viewer->current_paragraph];
1549 $load_node->(@{shift @future});
1550 },
1551 );
1552 }
1553
1554 $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 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1565 $load_node->($node);
1566 },
1567 );
1568 $buttons->add (new CFPlus::UI::Label text => "/");
1569 }
1570
1571 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1572
1573 $curnode = $node;
1574
1575 $viewer->clear;
1576 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1577 $viewer->scroll_to ($para);
1578 };
1579
1580 $load_node->(CFPlus::Pod::find pod => "mainpage");
1581
1582 $CFPlus::Pod::goto_document = sub {
1583 my (@path) = @_;
1584
1585 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1586
1587 $load_node->((CFPlus::Pod::find @path)[0]);
1588 $win->show;
1589 };
1590
1591 $win
1592 }
1593
1594 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 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 $table->add_at (0, 0, new CFPlus::UI::Button
1688 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
1693 $table->add_at (2, 0, new CFPlus::UI::Button
1694 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
1702 $dialog->show;
1703 };
1704 }
1705
1706 sub sdl_init {
1707 CFPlus::SDL_Init
1708 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 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1717 $FULLSCREEN = $CFG->{fullscreen};
1718 $FAST = $CFG->{fast};
1719
1720 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
1721 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1722
1723 $SDL_ACTIVE = 1;
1724 $LAST_REFRESH = time - 0.01;
1725
1726 CFPlus::OpenGL::init;
1727
1728 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1729
1730 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1731
1732 #############################################################################
1733
1734 if ($DEBUG_STATUS) {
1735 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1736 } else {
1737 # create the widgets
1738
1739 $DEBUG_STATUS = new CFPlus::UI::Label
1740 padding => 0,
1741 z => 100,
1742 force_x => "max",
1743 force_y => 0;
1744 $DEBUG_STATUS->show;
1745
1746 $STATUSBOX = new CFPlus::UI::Statusbox;
1747 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1748
1749 (new CFPlus::UI::Frame
1750 bg => [0, 0, 0, 0.4],
1751 force_x => 0,
1752 force_y => "max",
1753 child => $STATUSBOX,
1754 )->show;
1755
1756 CFPlus::UI::Toplevel->new (
1757 title => "Map",
1758 name => "mapmap",
1759 x => 0,
1760 y => $FONTSIZE + 8,
1761 border_bg => [1, 1, 1, 192/255],
1762 bg => [1, 1, 1, 0],
1763 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1764 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1765 ),
1766 )->show;
1767
1768 $MAPWIDGET = new CFPlus::MapWidget;
1769 $MAPWIDGET->connect (activate_console => sub {
1770 my ($mapwidget, $preset) = @_;
1771
1772 if ($CONSOLE) {
1773 $CONSOLE->{input}->{auto_activated} = 1;
1774 $CONSOLE->{input}->grab_focus;
1775
1776 if ($preset && $CONSOLE->{input}->get_text eq '') {
1777 $CONSOLE->{input}->set_text ($preset);
1778 }
1779 }
1780 });
1781 $MAPWIDGET->show;
1782 $MAPWIDGET->grab_focus;
1783
1784 $LOGVIEW = new CFPlus::UI::TextScroller
1785 expand => 1,
1786 font => $FONT_FIXED,
1787 fontsize => $::CFG->{log_fontsize},
1788 indent => -4,
1789 can_hover => 1,
1790 can_events => 1,
1791 max_par => $CFG->{logview_max_par},
1792 tooltip => "<b>Server Log</b>. This text viewer contains all recent messages sent by the server.",
1793 ;
1794
1795 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1796 title => "Setup",
1797 name => "setup_dialog",
1798 x => 'center',
1799 y => 'center',
1800 z => 2,
1801 force_w => $::WIDTH * 0.6,
1802 force_h => $::HEIGHT * 0.6,
1803 has_close_button => 1,
1804 ;
1805
1806 $METASERVER = metaserver_dialog;
1807
1808 $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
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 $SETUP_NOTEBOOK->add (Client => client_setup,
1814 "Configure various client-specific settings.");
1815 $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 "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 . "with nothing set and the recording started. After doing the actions you "
1823 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1824 . "After pressing the combo the binding will be saved automatically and the "
1825 . "binding editor closes");
1826 $SETUP_NOTEBOOK->add (Debug => debug_setup,
1827 "Some debuggin' options. Do not ask.");
1828
1829 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1830
1831 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1832 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1833
1834 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW = message_window,
1835 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 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1840 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1841
1842 $BUTTONBAR->add (new CFPlus::UI::Button
1843 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 on_activate => sub {
1846 $::CFG->{layout} = CFPlus::UI::get_layout;
1847 CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1848 status "Configuration Saved";
1849 0
1850 },
1851 );
1852
1853 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1854 tooltip => "View Documentation");
1855
1856
1857 $BUTTONBAR->add (new CFPlus::UI::Button
1858 text => "Quit",
1859 tooltip => "Terminates the program",
1860 on_activate => sub {
1861 if ($CONN) {
1862 open_quit_dialog;
1863 } else {
1864 exit;
1865 }
1866 0
1867 },
1868 );
1869
1870 $BUTTONBAR->show;
1871 $SETUP_DIALOG->show;
1872 }
1873
1874 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1875 }
1876
1877 sub setup_build_button {
1878 my ($enabled) = @_;
1879 if ($enabled) {
1880 $BUILD_BUTTON->hide if $BUILD_BUTTON;
1881 $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 $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
1930 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 $vb->add (
1938 new CFPlus::UI::Button text => $info->{build_arch_name},
1939 on_activate => sub {
1940 $::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 }
1946 );
1947 }
1948
1949 $win->show;
1950 }
1951
1952 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 $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 }
1973 if ($info->{has_text}) {
1974 $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 }
1977 if ($info->{has_connection}) {
1978 $table->add_at (0, $row, new CFPlus::UI::Label text => "Connection ID:");
1979 $table->add_at (1, $row++,
1980 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 sub video_shutdown {
1997 CFPlus::OpenGL::shutdown;
1998
1999 undef $SDL_ACTIVE;
2000 }
2001
2002 sub audio_channel_finished {
2003 my ($channel) = @_;
2004
2005 #warn "channel $channel finished\n";#d#
2006 }
2007
2008 sub audio_music_set {
2009 my ($songs) = @_;
2010
2011 my @want =
2012 grep $_,
2013 map $CONN->{music_meta}{$_},
2014 @$songs;
2015
2016 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
2044 delete $MUSIC_PLAYING->{stop_time};
2045 delete $MUSIC_PLAYING->{stop_pos};
2046 }
2047 }
2048
2049 sub audio_music_changed {
2050 return unless $CFG->{bgm_enable};
2051
2052 # default MUSIC_WANT == MUSIC_DEFAULT
2053 @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 return if $MUSIC_PLAYING
2057 && grep $MUSIC_PLAYING->{path} eq $_->{path}, @MUSIC_WANT;
2058
2059 my $NOW = time;
2060
2061 if ($MUSIC_PLAYING) {
2062 $MUSIC_PLAYING->{stop_time} = $NOW;
2063 $MUSIC_PLAYING->{stop_pos} = $NOW - $MUSIC_START;
2064 CFPlus::MixMusic::fade_out 1000;
2065 } else {
2066 # sort by stop time, oldest first
2067 @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
2068
2069 # 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
2076 audio_music_start;
2077 }
2078 }
2079
2080 sub audio_music_finished {
2081 $MUSIC_PLAYING = undef;
2082 undef $MUSIC_PLAYER;
2083
2084 audio_music_changed;
2085 }
2086
2087 sub audio_init {
2088 if ($CFG->{audio_enable}) {
2089 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
2090 $SDL_MIXER = !CFPlus::Mix_OpenAudio;
2091
2092 unless ($SDL_MIXER) {
2093 status "Unable to open sound device: there will be no sound";
2094 return;
2095 }
2096
2097 CFPlus::Mix_AllocateChannels 8;
2098 CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
2099
2100 audio_music_finished;
2101
2102 local $_;
2103 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 $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 }
2117 } else {
2118 status "unable to open sound config: $!";
2119 }
2120 }
2121 }
2122
2123 sub audio_shutdown {
2124 CFPlus::Mix_CloseAudio if $SDL_MIXER;
2125 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 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
2140
2141 $CFPlus::UI::ROOT->draw;
2142
2143 $WANT_REFRESH = 0;
2144 $CAN_REFRESH = 0;
2145 $LAST_REFRESH = $NOW;
2146
2147 CFPlus::SDL_GL_SwapBuffers;
2148 }
2149
2150 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
2151 $NOW = time;
2152
2153 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2154 for CFPlus::poll_events;
2155
2156 if (%animate_object) {
2157 $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2158 ++$WANT_REFRESH;
2159 }
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 CFPlus::SDL_QUIT => sub {
2189 exit;
2190 },
2191 CFPlus::SDL_VIDEORESIZE => sub {
2192 },
2193 CFPlus::SDL_VIDEOEXPOSE => sub {
2194 CFPlus::UI::full_refresh;
2195 },
2196 CFPlus::SDL_ACTIVEEVENT => sub {
2197 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2198 # 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 # 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 },
2204 CFPlus::SDL_KEYDOWN => sub {
2205 if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2206 # alt-enter
2207 $FULLSCREEN_ENABLE->toggle;
2208 video_shutdown;
2209 video_init;
2210 } else {
2211 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2212 }
2213 },
2214 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 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 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2233 CFPlus::DB::Server::run;
2234
2235 CFPlus::UI::set_layout ($::CFG->{layout});
2236
2237 my %DEF_CFG = (
2238 sdl_mode => 0,
2239 width => 640,
2240 height => 480,
2241 fullscreen => 0,
2242 fast => 0,
2243 map_scale => 1,
2244 fow_enable => 1,
2245 fow_intensity => 0,
2246 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 );
2267
2268 while (my ($k, $v) = each %DEF_CFG) {
2269 $CFG->{$k} = $v unless exists $CFG->{$k};
2270 }
2271
2272 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2273 $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
2287 sdl_init;
2288
2289 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2290 @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2291 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2292
2293 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2294
2295 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2296
2297 {
2298 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2299 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 CFPlus::add_font $_ for @fonts;
2310
2311 CFPlus::pango_init;
2312
2313 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2314 $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2315
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 # my $layout = CFPlus::Layout->new ($rgba);
2325 # $layout->set_text ("hallo" x 100);
2326 # $layout->render;
2327 # }
2328 # my $t2 = Time::HiRes::time;
2329 # warn $t2-$t1;
2330 # }
2331
2332 $startup_done->();
2333
2334 video_init;
2335 audio_init;
2336 }
2337
2338 show_tip_of_the_day if $CFG->{show_tips};
2339
2340 Event::loop;
2341 #CFPlus::SDL_Quit;
2342 #CFPlus::_exit 0;
2343
2344 END {
2345 CFPlus::SDL_Quit;
2346 CFPlus::DB::Server::stop;
2347 }
2348
2349 =head1 NAME
2350
2351 cfplus - A Crossfire+ and Crossfire game client
2352
2353 =head1 SYNOPSIS
2354
2355 Just run it - no commandline arguments are supported.
2356
2357 =head1 USAGE
2358
2359 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2360 fullscreen and interactively.
2361
2362 =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 =head1 AUTHOR
2373
2374 Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2375
2376
2377