ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.175
Committed: Tue Jul 17 22:08:57 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.174: +0 -3 lines
Log Message:
be more robust; do not hide the message window or show the stats window on chargen

File Contents

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