ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.153
Committed: Tue Apr 24 01:13:54 2007 UTC (17 years, 2 months ago) by root
Branch: MAIN
Changes since 1.152: +6 -8 lines
Log Message:
make use of new msg command to provide better feedback on character creation: http://data.plan9.de/chargen-nq8.png

File Contents

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