ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.180
Committed: Sat Jul 21 13:37:09 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.179: +4 -4 lines
Log Message:
fix... bug... doh....

File Contents

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