ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.186
Committed: Wed Jul 25 15:06:21 2007 UTC (16 years, 10 months ago) by elmex
Branch: MAIN
Changes since 1.185: +0 -1 lines
Log Message:
fixed some issues with the tabs

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 => "Tip of the day");
1190     $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1191 root 1.119 state => $CFG->{show_tips},
1192     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1193     on_changed => sub {
1194     my ($self, $value) = @_;
1195 root 1.121 $CFG->{show_tips} = $value;
1196 root 1.119 0
1197     }
1198     );
1199    
1200 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1201     $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
1202 root 1.149 text => $CFG->{logview_max_par},
1203     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1204     . "sends more messages than this number, older messages get removed to save memory and "
1205     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1206     on_changed => sub {
1207     my ($self, $value) = @_;
1208 elmex 1.182 $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1209 root 1.149 0
1210     },
1211 root 1.67 );
1212    
1213 root 1.149 $table
1214 root 1.1 }
1215    
1216 root 1.49 sub autopickup_setup {
1217 root 1.170 my $r = new CFPlus::UI::ScrolledWindow (
1218     expand => 1,
1219     scroll_y => 1
1220     );
1221     $r->add (my $table = new CFPlus::UI::Table
1222     row_expand => [0],
1223     col_expand => [0, 1, 0, 1],
1224     );
1225 elmex 1.44
1226 elmex 1.43 for (
1227 root 1.51 ["General", 0, 0,
1228 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1229 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1230     ["Stop before pickup" => PICKUP_STOP],
1231     ["Debug autopickup" => PICKUP_DEBUG],
1232 root 1.51 ],
1233     ["Weapons", 0, 6,
1234 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1235     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1236     ["Bows" => PICKUP_BOW],
1237     ["Arrows" => PICKUP_ARROW],
1238 root 1.51 ],
1239     ["Armour", 0, 12,
1240 root 1.58 ["Helmets" => PICKUP_HELMET],
1241     ["Shields" => PICKUP_SHIELD],
1242     ["Body Armour" => PICKUP_ARMOUR],
1243     ["Boots" => PICKUP_BOOTS],
1244     ["Gloves" => PICKUP_GLOVES],
1245     ["Cloaks" => PICKUP_CLOAK],
1246 root 1.51 ],
1247    
1248 root 1.133 ["Readables", 2, 0,
1249 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1250     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1251     ["Normal Books/Scrolls" => PICKUP_READABLES],
1252 root 1.51 ],
1253 root 1.133 ["Misc", 2, 5,
1254 root 1.58 ["Food" => PICKUP_FOOD],
1255     ["Drinks" => PICKUP_DRINK],
1256     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1257     ["Keys" => PICKUP_KEY],
1258     ["Magical Items" => PICKUP_MAGICAL],
1259     ["Potions" => PICKUP_POTION],
1260     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1261     ["Ignore cursed" => PICKUP_NOT_CURSED],
1262     ["Jewelery" => PICKUP_JEWELS],
1263 root 1.133 ["Flesh" => PICKUP_FLESH],
1264 root 1.51 ],
1265 elmex 1.66 ["Weight/Value ratio", 2, 17]
1266 elmex 1.43 )
1267     {
1268 root 1.51 my ($title, $x, $y, @bits) = @$_;
1269 root 1.178 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1270 root 1.51
1271     for (@bits) {
1272     ++$y;
1273    
1274 elmex 1.43 my $mask = $_->[1];
1275 root 1.178 $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1276     $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1277 elmex 1.83 state => $::CFG->{pickup} & $mask,
1278 elmex 1.43 on_changed => sub {
1279     my ($box, $value) = @_;
1280 root 1.63
1281 elmex 1.43 if ($value) {
1282 elmex 1.45 $::CFG->{pickup} |= $mask;
1283 elmex 1.43 } else {
1284 root 1.63 $::CFG->{pickup} &= ~$mask;
1285 elmex 1.43 }
1286 root 1.63
1287     $::CONN->send_command ("pickup $::CFG->{pickup}")
1288 elmex 1.45 if defined $::CONN;
1289 root 1.74
1290     0
1291 elmex 1.43 });
1292 root 1.86
1293     ${$_->[2]} = $checkbox if $_->[2];
1294 elmex 1.43 }
1295     }
1296    
1297 root 1.178 $table->add_at (2, 18, new CFPlus::UI::ValSlider
1298 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1299     template => ">= 99",
1300 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1301     on_changed => sub {
1302     my ($slider, $value) = @_;
1303    
1304 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1305 elmex 1.66 $::CFG->{pickup} |= int $value
1306     if $value;
1307     1;
1308     });
1309 elmex 1.83
1310 root 1.178 $table->add_at (3, 18, new CFPlus::UI::Button
1311 elmex 1.66 text => "set",
1312     on_activate => sub {
1313     $::CONN->send_command ("pickup $::CFG->{pickup}")
1314     if defined $::CONN;
1315 root 1.74 0
1316 elmex 1.66 });
1317    
1318 root 1.170 $r
1319 elmex 1.43 }
1320    
1321 root 1.102 my %SORT_ORDER = (
1322     type => undef,
1323 root 1.130 mtime => sub {
1324     my $NOW = time;
1325     sort {
1326     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1327     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1328    
1329     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1330     or $btime <=> $atime
1331     or $a->{type} <=> $b->{type}
1332     } @_
1333     },
1334 root 1.102 weight => sub { sort {
1335     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1336     or $a->{type} <=> $b->{type}
1337     } @_ },
1338     );
1339    
1340 elmex 1.85 sub inventory_widget {
1341 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1342 root 1.1
1343 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1344     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1345 root 1.99
1346 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1347 root 1.99
1348     use sort 'stable';
1349    
1350 root 1.104 $hb1->add (new CFPlus::UI::Selector
1351 root 1.102 value => $::CFG->{inv_sort},
1352 root 1.99 options => [
1353 root 1.102 [type => "Type/Name"],
1354     [mtime => "Recent/Normal/Locked"],
1355     [weight => "Weight/Type"],
1356 root 1.99 ],
1357     on_changed => sub {
1358 root 1.102 $::CFG->{inv_sort} = $_[1];
1359     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1360 root 1.99 },
1361     );
1362 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1363 root 1.99 #TODO# update to weigh/maxweight
1364 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1365 root 1.99
1366 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1367     $sw1->add ($INV = new CFPlus::UI::Inventory);
1368 root 1.127 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1369 root 1.1
1370 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1371 elmex 1.17
1372 root 1.104 $vb2->add ($INV_RIGHT_HB = new CFPlus::UI::HBox);
1373 elmex 1.14
1374 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1375     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1376 root 1.1
1377 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1378 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1379 elmex 1.27
1380 elmex 1.85 $hb
1381 root 1.1 }
1382    
1383 root 1.86 sub toggle_player_page {
1384     my ($widget) = @_;
1385    
1386     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1387     $PL_WINDOW->hide;
1388     } else {
1389     $PL_NOTEBOOK->set_current_page ($widget);
1390     $PL_WINDOW->show;
1391     }
1392     }
1393    
1394 elmex 1.85 sub player_window {
1395 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1396 elmex 1.85 x => "center",
1397     y => "center",
1398     force_w => $WIDTH * 9/10,
1399     force_h => $HEIGHT * 9/10,
1400     title => "Player",
1401 elmex 1.90 name => "playerbook",
1402 elmex 1.85 has_close_button => 1
1403     ;
1404    
1405     my $ntb =
1406     $PL_NOTEBOOK =
1407 root 1.157 new CFPlus::UI::Notebook expand => 1;
1408 root 1.86
1409 root 1.184 $ntb->add_tab (
1410 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1411 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1412     );
1413 root 1.184 $ntb->add_tab (
1414 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1415 elmex 1.92 "Shows all your Skills."
1416 elmex 1.85 );
1417 elmex 1.97
1418 root 1.131 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1419     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1420 root 1.184 $ntb->add_tab (
1421 elmex 1.97 "Spellbook (F4)" => $spellsw,
1422 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1423 elmex 1.85 );
1424 root 1.184 $ntb->add_tab (
1425 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1426 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1427     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1428 elmex 1.85 );
1429 root 1.184 $ntb->add_tab (Pickup => autopickup_setup,
1430 root 1.155 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1431 elmex 1.85
1432 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1433 root 1.86
1434 elmex 1.85 $plwin->add ($ntb);
1435     $plwin
1436 elmex 1.38 }
1437    
1438 root 1.49 sub keyboard_setup {
1439 root 1.137 CFPlus::Macro::keyboard_setup
1440 elmex 1.24 }
1441    
1442 root 1.64 sub help_window {
1443 root 1.114 my $win = new CFPlus::UI::Toplevel
1444 root 1.41 x => 'center',
1445     y => 'center',
1446 root 1.119 z => 4,
1447 root 1.41 name => 'doc_browser',
1448     force_w => int $WIDTH * 7/8,
1449     force_h => int $HEIGHT * 7/8,
1450 root 1.87 title => "Help Browser",
1451     has_close_button => 1;
1452 root 1.1
1453 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1454 root 1.1
1455 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1456     label => "Navigation",
1457     child => (my $buttons = new CFPlus::UI::HBox),
1458     );
1459 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1460 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1461 root 1.1
1462 root 1.111 my @history;
1463     my @future;
1464 root 1.112 my $curnode;
1465 root 1.111
1466     my $load_node; $load_node = sub {
1467 root 1.115 my ($node, $para) = @_;
1468 root 1.111
1469     $buttons->clear;
1470    
1471 root 1.116 $buttons->add (new CFPlus::UI::Button
1472     text => "⇤",
1473     tooltip => "back to the starting page",
1474     on_activate => sub {
1475     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1476     unshift @future, @history;
1477     @history = ();
1478     $load_node->(@{shift @future});
1479     },
1480     );
1481    
1482 root 1.111 if (@history) {
1483 root 1.112 $buttons->add (new CFPlus::UI::Button
1484 root 1.113 text => "⋘",
1485 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1486 root 1.112 on_activate => sub {
1487 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1488     $load_node->(@{pop @history});
1489 root 1.113 },
1490     );
1491 root 1.112 }
1492 root 1.111
1493 root 1.112 if (@future) {
1494     $buttons->add (new CFPlus::UI::Button
1495 root 1.113 text => "â‹™",
1496 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1497 root 1.112 on_activate => sub {
1498 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1499     $load_node->(@{shift @future});
1500 root 1.113 },
1501     );
1502 root 1.111 }
1503 root 1.112
1504 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1505    
1506     my @path = CFPlus::Pod::full_path_of $node;
1507     pop @path; # drop current node
1508    
1509     for my $node (@path) {
1510     $buttons->add (new CFPlus::UI::Button
1511     text => $node->{kw}[0],
1512     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1513     on_activate => sub {
1514 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1515 root 1.113 $load_node->($node);
1516     },
1517     );
1518     $buttons->add (new CFPlus::UI::Label text => "/");
1519     }
1520    
1521 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1522 root 1.113
1523 root 1.112 $curnode = $node;
1524    
1525 root 1.111 $viewer->clear;
1526 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1527 root 1.115 $viewer->scroll_to ($para);
1528 root 1.111 };
1529    
1530     $load_node->(CFPlus::Pod::find pod => "mainpage");
1531    
1532 root 1.113 $CFPlus::Pod::goto_document = sub {
1533 root 1.111 my (@path) = @_;
1534    
1535 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1536 root 1.111
1537 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1538 root 1.111 $win->show;
1539     };
1540 root 1.1
1541     $win
1542     }
1543    
1544 root 1.119 sub open_string_query {
1545     my ($title, $cb, $txt, $tooltip) = @_;
1546     my $dialog = new CFPlus::UI::Toplevel
1547     x => "center",
1548     y => "center",
1549     z => 50,
1550     force_w => $WIDTH * 4/5,
1551     title => $title;
1552    
1553     $dialog->add (
1554     my $e = new CFPlus::UI::Entry
1555     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1556     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1557     tooltip => $tooltip
1558     );
1559    
1560     $e->grab_focus;
1561     $e->set_text ($txt) if $txt;
1562     $dialog->show;
1563     }
1564    
1565     sub open_quit_dialog {
1566     unless ($QUIT_DIALOG) {
1567     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1568     x => "center",
1569     y => "center",
1570     z => 50,
1571     title => "Really Quit?",
1572     on_key_down => sub {
1573     my ($dialog, $ev) = @_;
1574     $ev->{sym} == 27 and $dialog->hide;
1575     }
1576     ;
1577    
1578     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1579    
1580     $vb->add (new CFPlus::UI::Label
1581     text => "You should find a savebed and apply it first!",
1582     max_w => $WIDTH * 0.25,
1583     ellipsize => 0,
1584     );
1585     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1586     $hb->add (new CFPlus::UI::Button
1587     text => "Ok",
1588     expand => 1,
1589     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1590     );
1591     $hb->add (new CFPlus::UI::Button
1592     text => "Quit anyway",
1593     expand => 1,
1594     on_activate => sub { exit },
1595     );
1596     }
1597    
1598     $QUIT_DIALOG->show;
1599     $QUIT_DIALOG->grab_focus;
1600     }
1601    
1602     sub show_tip_of_the_day {
1603     # find all tips
1604     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1605    
1606 root 1.148 CFPlus::DB::get state => "tip_of_the_day", sub {
1607     my ($todindex) = @_;
1608     $todindex = 0 if $todindex >= @tod;
1609     CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
1610    
1611     # create dialog
1612     my $dialog;
1613    
1614     my $close = sub {
1615     $dialog->destroy;
1616     };
1617    
1618     $dialog = new CFPlus::UI::Toplevel
1619     x => "center",
1620     y => "center",
1621     z => 3,
1622     name => 'tip_of_the_day',
1623     force_w => int $WIDTH * 4/9,
1624     force_h => int $WIDTH * 2/9,
1625     title => "Tip of the day #" . (1 + $todindex),
1626     child => my $vbox = new CFPlus::UI::VBox,
1627     has_close_button => 1,
1628     on_delete => $close,
1629     ;
1630    
1631     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1632     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1633     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1634    
1635     $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
1636    
1637 root 1.178 $table->add_at (0, 0, new CFPlus::UI::Button
1638 root 1.148 text => "Close",
1639     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>.",
1640     on_activate => $close,
1641     );
1642 root 1.119
1643 root 1.178 $table->add_at (2, 0, new CFPlus::UI::Button
1644 root 1.148 text => "Next",
1645     tooltip => "Show the next <b>Tip of the day</b>.",
1646     on_activate => sub {
1647     $close->();
1648     &show_tip_of_the_day;
1649     },
1650     );
1651 root 1.119
1652 root 1.148 $dialog->show;
1653 root 1.119 };
1654     }
1655    
1656 root 1.1 sub sdl_init {
1657 root 1.104 CFPlus::SDL_Init
1658 root 1.1 and die "SDL::Init failed!\n";
1659     }
1660    
1661     sub video_init {
1662     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1663    
1664     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1665    
1666 root 1.172 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1667 root 1.1 $FULLSCREEN = $CFG->{fullscreen};
1668     $FAST = $CFG->{fast};
1669    
1670 root 1.172 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
1671 root 1.104 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1672 root 1.1
1673     $SDL_ACTIVE = 1;
1674     $LAST_REFRESH = time - 0.01;
1675    
1676 root 1.104 CFPlus::OpenGL::init;
1677 root 1.1
1678     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1679    
1680 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1681 root 1.1
1682     #############################################################################
1683    
1684     if ($DEBUG_STATUS) {
1685 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1686 root 1.1 } else {
1687     # create the widgets
1688    
1689 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1690 root 1.30 padding => 0,
1691     z => 100,
1692     force_x => "max",
1693     force_y => 0;
1694 root 1.1 $DEBUG_STATUS->show;
1695 elmex 1.34
1696 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1697 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1698 root 1.1
1699 root 1.104 (new CFPlus::UI::Frame
1700 root 1.1 bg => [0, 0, 0, 0.4],
1701 root 1.30 force_x => 0,
1702     force_y => "max",
1703 root 1.1 child => $STATUSBOX,
1704     )->show;
1705    
1706 root 1.114 CFPlus::UI::Toplevel->new (
1707 root 1.47 title => "Map",
1708 root 1.42 name => "mapmap",
1709 root 1.30 x => 0,
1710     y => $FONTSIZE + 8,
1711 root 1.1 border_bg => [1, 1, 1, 192/255],
1712     bg => [1, 1, 1, 0],
1713 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
1714 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1715     ),
1716     )->show;
1717    
1718 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
1719 root 1.1 $MAPWIDGET->connect (activate_console => sub {
1720     my ($mapwidget, $preset) = @_;
1721    
1722 elmex 1.182 $MESSAGE_WINDOW->activate_console ($preset)
1723     if $MESSAGE_WINDOW;
1724 root 1.1 });
1725     $MAPWIDGET->show;
1726 root 1.74 $MAPWIDGET->grab_focus;
1727 root 1.1
1728 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
1729 root 1.49 title => "Setup",
1730     name => "setup_dialog",
1731     x => 'center',
1732     y => 'center',
1733 root 1.53 z => 2,
1734 root 1.49 force_w => $::WIDTH * 0.6,
1735     force_h => $::HEIGHT * 0.6,
1736 root 1.74 has_close_button => 1,
1737 root 1.49 ;
1738    
1739 elmex 1.81 $METASERVER = metaserver_dialog;
1740 elmex 1.182 $MESSAGE_WINDOW = new CFPlus::UI::MessageWindow;
1741 elmex 1.81
1742 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
1743     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1744 root 1.49
1745 root 1.184 $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
1746 root 1.49 "Configure the server to play on, your username, password and other server-related options.");
1747 root 1.184 $SETUP_NOTEBOOK->add_tab (Client => client_setup,
1748 root 1.149 "Configure various client-specific settings.");
1749 root 1.184 $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
1750 root 1.49 "Configure the video mode, performance, fonts and other graphical aspects of the game.");
1751 root 1.184 $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
1752 root 1.49 "Configure the use of audio, sound effects and background music.");
1753 root 1.184 $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
1754 root 1.75 "Lets you define, edit and delete key bindings."
1755     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
1756 root 1.49 . "with nothing set and the recording started. After doing the actions you "
1757 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
1758 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
1759     . "binding editor closes");
1760 root 1.184 $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
1761 root 1.75 "Some debuggin' options. Do not ask.");
1762 root 1.49
1763 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
1764 root 1.1
1765 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
1766 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
1767    
1768 elmex 1.182 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
1769 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1770    
1771     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
1772    
1773 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
1774 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
1775 root 1.1
1776 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1777 root 1.1 text => "Save Config",
1778     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1779 root 1.18 on_activate => sub {
1780 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
1781     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
1782 root 1.1 status "Configuration Saved";
1783 root 1.74 0
1784 root 1.1 },
1785     );
1786    
1787 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
1788 root 1.1 tooltip => "View Documentation");
1789    
1790 elmex 1.134
1791 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
1792 root 1.18 text => "Quit",
1793     tooltip => "Terminates the program",
1794     on_activate => sub {
1795 root 1.1 if ($CONN) {
1796     open_quit_dialog;
1797     } else {
1798     exit;
1799     }
1800 root 1.74 0
1801 root 1.1 },
1802     );
1803    
1804     $BUTTONBAR->show;
1805 root 1.49 $SETUP_DIALOG->show;
1806     }
1807 root 1.1
1808 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1809 root 1.1 }
1810    
1811 elmex 1.134 sub setup_build_button {
1812     my ($enabled) = @_;
1813     if ($enabled) {
1814 elmex 1.140 $BUILD_BUTTON->hide if $BUILD_BUTTON;
1815 elmex 1.134 $BUILD_BUTTON ||= new CFPlus::UI::Button
1816     text => "Build",
1817     tooltip => "Opens the ingame builder",
1818     on_activate => sub {
1819     if ($CONN) {
1820     $CONN->send_ext_req (builder_player_items => sub {
1821     open_ingame_editor ($_[0]) if exists $_[0]->{items};
1822     });
1823     }
1824     0
1825     };
1826     $BUTTONBAR->add ($BUILD_BUTTON);
1827     } else {
1828     $BUILD_BUTTON->hide if $BUILD_BUTTON;
1829     }
1830     }
1831    
1832     sub open_ingame_editor {
1833     my ($msg) = @_;
1834    
1835     my $win = new CFPlus::UI::Toplevel
1836     x => 0,
1837     y => 'center',
1838     z => 4,
1839     name => 'builder_window',
1840     force_w => int $WIDTH * 1/4,
1841     force_h => int $HEIGHT * 3/4,
1842     title => "In game builder",
1843     has_close_button => 1;
1844    
1845     my $r = new CFPlus::UI::ScrolledWindow (
1846     expand => 1,
1847     scroll_y => 1
1848     );
1849     $r->add (my $vb = new CFPlus::UI::VBox);
1850     $win->add ($r);
1851    
1852    
1853 elmex 1.135 $vb->add (
1854     new CFPlus::UI::Button
1855     text => "Disable build mode",
1856     on_activate => sub { $::IN_BUILD_MODE = undef }
1857     );
1858     $vb->add (
1859     new CFPlus::UI::Button
1860     text => "ERASE",
1861     on_activate => sub { $::IN_BUILD_MODE = { do_erase => 1 } }
1862     );
1863 elmex 1.134
1864 elmex 1.135 for my $itemarchname (
1865     sort {
1866     $msg->{items}->{$a}->{build_arch_name}
1867     cmp $msg->{items}->{$b}->{build_arch_name}
1868     } keys %{$msg->{items}}
1869     ) {
1870     my $info = $msg->{items}->{$itemarchname};
1871 elmex 1.134 $vb->add (
1872 elmex 1.135 new CFPlus::UI::Button text => $info->{build_arch_name},
1873 elmex 1.134 on_activate => sub {
1874 elmex 1.135 $::IN_BUILD_MODE = { item => $itemarchname, info => $info };
1875    
1876     if (grep { $msg->{items}->{$itemarchname}->{$_} } qw/has_connection has_name has_text/) {
1877     build_mode_query_arch_info ();
1878     }
1879 elmex 1.134 }
1880     );
1881     }
1882    
1883     $win->show;
1884     }
1885    
1886 elmex 1.135 sub build_mode_query_arch_info {
1887     my ($iteminfo) = $::IN_BUILD_MODE;
1888     my $itemarchname = $iteminfo->{item};
1889     my $info = $iteminfo->{info};
1890    
1891     my $dialog = new CFPlus::UI::Toplevel
1892     x => "center",
1893     y => "center",
1894     z => 50,
1895     force_w => int $WIDTH * 1/2,
1896     title => "Enter information for placement of '$itemarchname'",
1897     has_close_button => 1;
1898    
1899     $dialog->add (my $vb = new CFPlus::UI::VBox expand => 1);
1900    
1901     $vb->add (my $table = new CFPlus::UI::Table expand => 1);
1902     my $row = 0;
1903     if ($info->{has_name}) {
1904 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label text => "Name:");
1905     $table->add_at (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{name} = $_[1]; 0 });
1906 elmex 1.135 }
1907     if ($info->{has_text}) {
1908 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label text => "Text:");
1909     $table->add_at (1, $row++, new CFPlus::UI::Entry expand => 1, on_changed => sub { $::IN_BUILD_MODE->{text} = $_[1]; 0 });
1910 elmex 1.135 }
1911     if ($info->{has_connection}) {
1912 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label text => "Connection ID:");
1913     $table->add_at (1, $row++,
1914 elmex 1.135 new CFPlus::UI::Entry
1915     expand => 1,
1916     on_changed => sub { $::IN_BUILD_MODE->{connection} = $_[1]; 0 },
1917     tooltip => "Enter the connection ID here. The connection ID connects actors like a lever to a gate or a magic ear to a gate"
1918     );
1919     }
1920    
1921     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1922     $hb->add (new CFPlus::UI::Button
1923     text => "Close",
1924     expand => 1,
1925     on_activate => sub { $dialog->hide; 0 },
1926     );
1927     $dialog->show;
1928     }
1929    
1930 root 1.1 sub video_shutdown {
1931 root 1.104 CFPlus::OpenGL::shutdown;
1932 root 1.73
1933 root 1.1 undef $SDL_ACTIVE;
1934     }
1935    
1936     sub audio_channel_finished {
1937     my ($channel) = @_;
1938    
1939     #warn "channel $channel finished\n";#d#
1940     }
1941    
1942 root 1.163 sub audio_music_set {
1943     my ($songs) = @_;
1944    
1945 root 1.168 my @want =
1946     grep $_,
1947     map $CONN->{music_meta}{$_},
1948     @$songs;
1949 root 1.163
1950 root 1.168 if (@want) {
1951     @MUSIC_WANT = @want;
1952     &audio_music_changed ();
1953     }
1954     }
1955    
1956     sub audio_music_start {
1957     my $path = $MUSIC_PLAYING->{path}
1958     or return;
1959    
1960     CFPlus::DB::prefetch_file $path, 1024_000, sub {
1961 root 1.183 return unless $SDL_MIXER;
1962    
1963 root 1.168 # music might have changed...
1964     $path eq $MUSIC_PLAYING->{path}
1965     or return &audio_music_start ();
1966    
1967     $MUSIC_PLAYER = new_from_file CFPlus::MixMusic $path;
1968    
1969     my $NOW = time;
1970    
1971     if ($MUSIC_PLAYING->{stop_time} > $NOW - $MUSIC_RESUME) {
1972     my $pos = $MUSIC_PLAYING->{stop_pos};
1973     $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
1974     $MUSIC_START = time - $pos;
1975     } else {
1976     $MUSIC_PLAYER->play (0);
1977     $MUSIC_START = time;
1978     }
1979 root 1.163
1980 root 1.168 delete $MUSIC_PLAYING->{stop_time};
1981     delete $MUSIC_PLAYING->{stop_pos};
1982 root 1.163 }
1983     }
1984    
1985     sub audio_music_changed {
1986 root 1.1 return unless $CFG->{bgm_enable};
1987 root 1.183 return unless $SDL_MIXER;
1988 root 1.1
1989 root 1.165 # default MUSIC_WANT == MUSIC_DEFAULT
1990 root 1.163 @MUSIC_WANT = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_WANT;
1991    
1992     # if the currently playing song is acceptable, let it continue
1993 root 1.164 return if $MUSIC_PLAYING
1994     && grep $MUSIC_PLAYING->{path} eq $_->{path}, @MUSIC_WANT;
1995 root 1.163
1996 root 1.168 my $NOW = time;
1997    
1998 root 1.164 if ($MUSIC_PLAYING) {
1999 root 1.168 $MUSIC_PLAYING->{stop_time} = $NOW;
2000     $MUSIC_PLAYING->{stop_pos} = $NOW - $MUSIC_START;
2001 root 1.165 CFPlus::MixMusic::fade_out 1000;
2002 root 1.163 } else {
2003 root 1.168 # sort by stop time, oldest first
2004     @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
2005 root 1.163
2006 root 1.168 # if the most recently-played piece played very recently,
2007     # resume it, else choose the oldest piece for rotation.
2008     $MUSIC_PLAYING =
2009     $MUSIC_WANT[-1]{stop_time} > $NOW - $MUSIC_RESUME
2010     ? $MUSIC_WANT[-1]
2011     : $MUSIC_WANT[0];
2012 root 1.163
2013 root 1.168 audio_music_start;
2014 root 1.163 }
2015     }
2016    
2017     sub audio_music_finished {
2018     $MUSIC_PLAYING = undef;
2019     undef $MUSIC_PLAYER;
2020 root 1.1
2021 root 1.163 audio_music_changed;
2022 root 1.1 }
2023    
2024     sub audio_init {
2025     if ($CFG->{audio_enable}) {
2026 root 1.104 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
2027     $SDL_MIXER = !CFPlus::Mix_OpenAudio;
2028 root 1.1
2029     unless ($SDL_MIXER) {
2030     status "Unable to open sound device: there will be no sound";
2031     return;
2032     }
2033    
2034 root 1.104 CFPlus::Mix_AllocateChannels 8;
2035     CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
2036 root 1.1
2037     audio_music_finished;
2038    
2039 root 1.129 local $_;
2040 root 1.1 while (<$fh>) {
2041     next if /^\s*#/;
2042     next if /^\s*$/;
2043    
2044     my ($file, $volume, $event) = split /\s+/, $_, 3;
2045    
2046     push @SOUNDS, "$volume,$file";
2047    
2048 root 1.129 $AUDIO_CHUNKS{"$volume,$file"} ||= do {
2049     my $chunk = new_from_file CFPlus::MixChunk CFPlus::find_rcfile "sounds/$file";
2050     $chunk->volume ($volume * 128 / 100);
2051     $chunk
2052     };
2053 root 1.1 }
2054     } else {
2055     status "unable to open sound config: $!";
2056     }
2057 root 1.183 } else {
2058     undef $SDL_MIXER;
2059 root 1.1 }
2060     }
2061    
2062     sub audio_shutdown {
2063 root 1.104 CFPlus::Mix_CloseAudio if $SDL_MIXER;
2064 root 1.1 undef $SDL_MIXER;
2065     @SOUNDS = ();
2066     %AUDIO_CHUNKS = ();
2067     }
2068    
2069     my %animate_object;
2070     my $animate_timer;
2071    
2072     my $fps = 9;
2073    
2074     my %demo;#d#
2075    
2076     sub force_refresh {
2077     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
2078 root 1.33 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4;
2079 root 1.1
2080 root 1.104 $CFPlus::UI::ROOT->draw;
2081 root 1.1
2082     $WANT_REFRESH = 0;
2083 root 1.174 $CAN_REFRESH = 0;
2084 root 1.1 $LAST_REFRESH = $NOW;
2085    
2086 root 1.104 CFPlus::SDL_GL_SwapBuffers;
2087 root 1.1 }
2088    
2089 root 1.49 my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub {
2090 root 1.1 $NOW = time;
2091    
2092     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2093 root 1.169 for CFPlus::poll_events;
2094 root 1.1
2095     if (%animate_object) {
2096     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2097 root 1.174 ++$WANT_REFRESH;
2098 root 1.1 }
2099    
2100     if ($WANT_REFRESH) {
2101     force_refresh;
2102     } else {
2103     $CAN_REFRESH = 1;
2104     }
2105     });
2106    
2107     sub animation_start {
2108     my ($widget) = @_;
2109     $animate_object{$widget} = $widget;
2110     }
2111    
2112     sub animation_stop {
2113     my ($widget) = @_;
2114     delete $animate_object{$widget};
2115     }
2116    
2117     # check once/second for faces that need to be prefetched
2118     # this should, of course, only run on demand, but
2119     # SDL forces worse things on us....
2120    
2121     Event->timer (after => 1, interval => 0.25, cb => sub {
2122     $CONN->face_prefetch
2123     if $CONN;
2124     });
2125    
2126     %SDL_CB = (
2127 root 1.104 CFPlus::SDL_QUIT => sub {
2128 root 1.151 exit;
2129 root 1.1 },
2130 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2131 root 1.1 },
2132 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2133     CFPlus::UI::full_refresh;
2134 root 1.1 },
2135 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2136 root 1.137 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2137 root 1.171 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2138     # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2139 root 1.137 # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2140     # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2141     # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2142 root 1.1 },
2143 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2144     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2145 root 1.1 # alt-enter
2146 root 1.94 $FULLSCREEN_ENABLE->toggle;
2147 root 1.1 video_shutdown;
2148     video_init;
2149     } else {
2150 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2151 root 1.1 }
2152     },
2153 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2154     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2155     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2156     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2157     CFPlus::SDL_USEREVENT => sub {
2158 root 1.1 if ($_[0]{code} == 1) {
2159     audio_channel_finished $_[0]{data1};
2160     } elsif ($_[0]{code} == 0) {
2161     audio_music_finished;
2162     }
2163     },
2164     );
2165    
2166     #############################################################################
2167    
2168     $SIG{INT} = $SIG{TERM} = sub { exit };
2169    
2170     {
2171 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2172 root 1.147 CFPlus::DB::Server::run;
2173    
2174 root 1.104 CFPlus::UI::set_layout ($::CFG->{layout});
2175 root 1.1
2176     my %DEF_CFG = (
2177 root 1.149 sdl_mode => 0,
2178     width => 640,
2179     height => 480,
2180     fullscreen => 0,
2181     fast => 0,
2182     map_scale => 1,
2183     fow_enable => 1,
2184 root 1.173 fow_intensity => 0,
2185 root 1.149 map_smoothing => 1,
2186     gui_fontsize => 1,
2187     log_fontsize => 0.7,
2188     gauge_fontsize => 1,
2189     gauge_size => 0.35,
2190     stat_fontsize => 0.7,
2191     mapsize => 100,
2192     audio_enable => 1,
2193     bgm_enable => 1,
2194     bgm_volume => 0.25,
2195     face_prefetch => 0,
2196     output_sync => 1,
2197     output_count => 1,
2198     output_rate => "",
2199     pickup => 0,
2200     inv_sort => "mtime",
2201     default => "profile", # default profile
2202     show_tips => 1,
2203     logview_max_par => 1000,
2204 root 1.1 );
2205 root 1.75
2206 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2207     $CFG->{$k} = $v unless exists $CFG->{$k};
2208     }
2209    
2210 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2211 root 1.137 $PROFILE = $CFG->{profile}{default};
2212    
2213     # convert old bindings (only default profile matters)
2214     if (my $bindings = delete $PROFILE->{bindings}) {
2215     while (my ($mod, $syms) = each %$bindings) {
2216     while (my ($sym, $cmds) = each %$syms) {
2217     push @{ $PROFILE->{macro} }, {
2218     accelkey => [$mod*1, $sym*1],
2219     action => $cmds,
2220     };
2221     }
2222     }
2223     }
2224 root 1.75
2225 root 1.1 sdl_init;
2226    
2227 root 1.172 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2228     @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2229 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2230 root 1.1
2231 root 1.177 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2232    
2233 root 1.1 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2234    
2235     {
2236 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2237 root 1.1 DejaVuSans.ttf
2238     DejaVuSansMono.ttf
2239     DejaVuSans-Bold.ttf
2240     DejaVuSansMono-Bold.ttf
2241     DejaVuSans-Oblique.ttf
2242     DejaVuSansMono-Oblique.ttf
2243     DejaVuSans-BoldOblique.ttf
2244     DejaVuSansMono-BoldOblique.ttf
2245     );
2246    
2247 root 1.104 CFPlus::add_font $_ for @fonts;
2248 root 1.1
2249 root 1.104 CFPlus::pango_init;
2250 root 1.1
2251 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2252     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2253 root 1.1
2254     $FONT_PROP->make_default;
2255     }
2256    
2257     # compare mono (ft) vs. rgba (cairo)
2258     # ft - 1.8s, cairo 3s, even in alpha-only mode
2259     # for my $rgba (0..1) {
2260     # my $t1 = Time::HiRes::time;
2261     # for (1..1000) {
2262 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2263 root 1.1 # $layout->set_text ("hallo" x 100);
2264     # $layout->render;
2265     # }
2266     # my $t2 = Time::HiRes::time;
2267     # warn $t2-$t1;
2268     # }
2269    
2270 root 1.122 $startup_done->();
2271    
2272 root 1.1 video_init;
2273     audio_init;
2274     }
2275    
2276 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2277    
2278 root 1.1 Event::loop;
2279 root 1.104 #CFPlus::SDL_Quit;
2280     #CFPlus::_exit 0;
2281 root 1.1
2282 root 1.150 END {
2283 root 1.151 CFPlus::SDL_Quit;
2284 root 1.150 CFPlus::DB::Server::stop;
2285     }
2286 root 1.1
2287     =head1 NAME
2288    
2289 root 1.28 cfplus - A Crossfire+ and Crossfire game client
2290 root 1.1
2291     =head1 SYNOPSIS
2292    
2293     Just run it - no commandline arguments are supported.
2294    
2295     =head1 USAGE
2296    
2297 root 1.28 cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used
2298 root 1.1 fullscreen and interactively.
2299    
2300 root 1.39 =head1 DEBUGGING
2301    
2302    
2303     CFPLUS_DEBUG - environment variable
2304    
2305     1 draw borders around widgets
2306     2 add low-level widget info to tooltips
2307     4 show fps
2308     8 suppress tooltips
2309    
2310 root 1.1 =head1 AUTHOR
2311    
2312     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2313    
2314    
2315