ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.185
Committed: Tue Jul 24 18:39:39 2007 UTC (16 years, 10 months ago) by elmex
Branch: MAIN
Changes since 1.184: +1 -14 lines
Log Message:
chat tabs are now hopefully finally implemented for now

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