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

File Contents

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