ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.222
Committed: Sun Aug 26 03:12:34 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.221: +6 -3 lines
Log Message:
windows tweaks

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