ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.181
Committed: Sat Jul 21 16:07:53 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.180: +28 -1 lines
Log Message:
the beginning of a (very simple) canvas widget

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