ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.184
Committed: Mon Jul 23 15:30:46 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.183: +11 -11 lines
Log Message:
refactored notebook widget to use standard api, addec c_add/c_remove signals

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