ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.220
Committed: Tue Aug 21 02:39:17 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.219: +5 -2 lines
Log Message:
- new progress widget
- new experience progress widget
- took advantage of experience info and displayed "next level" info.
  needs tooltips with details..

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     $vbox->add (my $exp = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_exp");
990 root 1.220 $vbox->add (my $prg = new CFPlus::UI::ExperienceProgress);
991 root 1.109 $vbox->add (my $rng = new CFPlus::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1, tooltip => "#stat_ranged");
992 root 1.1
993     $GAUGES = {
994 root 1.220 exp => $exp, prg => $prg, win => $win, range => $rng,
995 root 1.1 food => $fg, mana => $mg, hp => $hg, grace => $gg
996     };
997    
998     &set_gauge_window_fontsize;
999    
1000     $win
1001     }
1002    
1003 root 1.65 sub debug_setup {
1004 root 1.104 my $table = new CFPlus::UI::Table;
1005 root 1.65
1006 root 1.178 $table->add_at (0, 0, new CFPlus::UI::Label text => "Widget Borders");
1007     $table->add_at (1, 0, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 1; 0 });
1008     $table->add_at (0, 1, new CFPlus::UI::Label text => "Tooltip Widget Info");
1009     $table->add_at (1, 1, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 2; 0 });
1010     $table->add_at (0, 2, new CFPlus::UI::Label text => "Show FPS");
1011     $table->add_at (1, 2, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 4; 0 });
1012     $table->add_at (0, 3, new CFPlus::UI::Label text => "Suppress Tooltips");
1013     $table->add_at (1, 3, new CFPlus::UI::CheckBox on_changed => sub { $ENV{CFPLUS_DEBUG} ^= 8; 0 });
1014     $table->add_at (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } );
1015 root 1.65
1016 root 1.178 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
1017 root 1.65
1018 root 1.181 $table->add_at (7,7, my $t = new CFPlus::UI::Table expand => 0);
1019 root 1.199 $t->add_at (0,0, new CFPlus::UI::Label text => "a a a a", c_rowspan => 1, c_colspan => 2);
1020     $t->add_at (2,0, new CFPlus::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1);
1021     $t->add_at (1,2, new CFPlus::UI::Label text => "c c c c", c_rowspan => 1, c_colspan => 2);
1022     $t->add_at (0,1, new CFPlus::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1);
1023 root 1.179 $t->add_at (1,1, new CFPlus::UI::Label text => "e");
1024    
1025 root 1.181 $table->add_at (7, 6, my $c = new CFPlus::UI::Canvas);
1026    
1027     $c->add_items ({
1028     type => "line_loop",
1029     color => [0, 1, 0],
1030     width => 9,
1031     coord_mode => "abs",
1032     coord => [[10, 5], [5, 50], [20, 5], [5, 60]],
1033     });
1034    
1035     $c->add_items ({
1036     type => "lines",
1037     color => [1, 1, 0],
1038     width => 2,
1039     coord_mode => "rel",
1040     coord => [[0,0], [1,1], [1,0], [0,1]],
1041     });
1042    
1043     $c->add_items ({
1044     type => "polygon",
1045     color => [0, 0.43, 0],
1046     width => 2,
1047     coord_mode => "rel",
1048     coord => [[0,0.2], [1,.4], [1,.6], [0,.8]],
1049     });
1050    
1051 root 1.65 $table
1052     }
1053 elmex 1.24
1054 root 1.60 sub stats_window {
1055 root 1.104 my $r = new CFPlus::UI::ScrolledWindow (
1056 root 1.98 expand => 1,
1057     scroll_y => 1
1058     );
1059 root 1.104 $r->add (my $vb = new CFPlus::UI::VBox);
1060 root 1.1
1061 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1062     label => "Player",
1063     child => (my $pi = new CFPlus::UI::VBox),
1064     );
1065    
1066     $pi->add ($STATWIDS->{title} = new CFPlus::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
1067 root 1.1 can_hover => 1, can_events => 1,
1068     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
1069 root 1.114 $pi->add ($STATWIDS->{map} = new CFPlus::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
1070 root 1.1 can_hover => 1, can_events => 1,
1071     tooltip => "The map you are currently on (if supported by the server).");
1072    
1073 root 1.114 $pi->add (my $hb0 = new CFPlus::UI::HBox);
1074 root 1.104 $hb0->add ($STATWIDS->{weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
1075 elmex 1.5 can_hover => 1, can_events => 1,
1076 root 1.15 tooltip => "The weight of the player including all inventory items.");
1077 root 1.104 $hb0->add ($STATWIDS->{m_weight} = new CFPlus::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
1078 elmex 1.5 can_hover => 1, can_events => 1,
1079 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
1080 elmex 1.5
1081 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1082     label => "Primary/Secondary Statistics",
1083     child => (my $hb = new CFPlus::UI::HBox expand => 1),
1084     );
1085 root 1.104 $hb->add (my $tbl = new CFPlus::UI::Table expand => 1);
1086 root 1.1
1087     my $color2 = [1, 1, 0];
1088    
1089     for (
1090 root 1.98 [0, 0, st_str => "Str", 30],
1091     [0, 1, st_dex => "Dex", 30],
1092     [0, 2, st_con => "Con", 30],
1093     [0, 3, st_int => "Int", 30],
1094     [0, 4, st_wis => "Wis", 30],
1095     [0, 5, st_pow => "Pow", 30],
1096     [0, 6, st_cha => "Cha", 30],
1097    
1098     [2, 0, st_wc => "Wc", -120],
1099     [2, 1, st_ac => "Ac", -120],
1100     [2, 2, st_dam => "Dam", 120],
1101     [2, 3, st_arm => "Arm", 120],
1102     [2, 4, st_spd => "Spd", 10.54],
1103     [2, 5, st_wspd => "WSp", 10.54],
1104 root 1.1 ) {
1105 root 1.98 my ($col, $row, $id, $label, $template) = @$_;
1106 root 1.1
1107 root 1.178 $tbl->add_at ($col , $row, $STATWIDS->{$id} = new CFPlus::UI::Label
1108 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0,
1109 root 1.107 align => +1, template => $template, tooltip => "#stat_$label");
1110 root 1.178 $tbl->add_at ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFPlus::UI::Label
1111 root 1.98 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0,
1112 root 1.107 align => -1, text => $label, tooltip => "#stat_$label");
1113 root 1.1 }
1114    
1115 root 1.114 $vb->add (new CFPlus::UI::FancyFrame
1116     label => "Resistancies",
1117     child => (my $tbl2 = new CFPlus::UI::Table expand => 1),
1118     );
1119 root 1.1
1120     my $row = 0;
1121     my $col = 0;
1122    
1123     my %resist_names = (
1124 elmex 1.92 slow => ["Slow",
1125     "<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.)"],
1126     holyw => ["Holy Word",
1127     "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)"],
1128     conf => ["Confusion",
1129     "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)"],
1130     fire => ["Fire",
1131     "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)"],
1132     depl => ["Depletion",
1133     "<b>Depletion</b> (some monsters and other effects can cause stats depletion)"],
1134     magic => ["Magic",
1135     "<b>Magic</b> (resistance to magic spells like magic missile or similar)"],
1136     drain => ["Draining",
1137     "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)"],
1138     acid => ["Acid",
1139     "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)"],
1140     pois => ["Poison",
1141     "<b>Poison</b> (resistance to getting poisoned)"],
1142     para => ["Paralysation",
1143     "<b>Paralysation</b> (this resistance affects the chance you get paralysed)"],
1144     deat => ["Death",
1145     "<b>Death</b> (resistance against death spells)"],
1146     phys => ["Physical",
1147     "<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.)"],
1148     blind => ["Blind",
1149     "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)"],
1150     fear => ["Fear",
1151     "<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)"],
1152     tund => ["Turn undead",
1153     "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead..."],
1154     elec => ["Electricity",
1155     "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)"],
1156     cold => ["Cold",
1157     "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)"],
1158     ghit => ["Ghost hit",
1159     "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)"],
1160 root 1.1 );
1161 root 1.178
1162 root 1.1 for (qw/slow holyw conf fire depl magic
1163     drain acid pois para deat phys
1164     blind fear tund elec cold ghit/)
1165     {
1166 root 1.178 $tbl2->add_at ($col, $row,
1167 root 1.1 $STATWIDS->{"res_$_"} =
1168 root 1.104 new CFPlus::UI::Label
1169 root 1.1 font => $FONT_FIXED,
1170     template => "-100%",
1171     align => +1,
1172     valign => 0,
1173     can_events => 1,
1174     can_hover => 1,
1175 elmex 1.92 tooltip => $resist_names{$_}->[1],
1176 root 1.1 );
1177 root 1.178 $tbl2->add_at ($col + 1, $row, new CFPlus::UI::Image
1178 root 1.1 font => $FONT_FIXED,
1179     can_hover => 1,
1180     can_events => 1,
1181 root 1.78 path => "ui/resist/resist_$_.png",
1182 elmex 1.92 tooltip => $resist_names{$_}->[1],
1183     );
1184 root 1.178 $tbl2->add_at ($col + 2, $row, new CFPlus::UI::Label
1185 elmex 1.92 text => $resist_names{$_}->[0],
1186     font => $FONT_FIXED,
1187     can_hover => 1,
1188     can_events => 1,
1189     tooltip => $resist_names{$_}->[1],
1190 root 1.1 );
1191    
1192     $row++;
1193     if ($row % 6 == 0) {
1194 elmex 1.92 $col += 3;
1195 root 1.1 $row = 0;
1196     }
1197     }
1198    
1199 root 1.95 #update_stats_window ({});
1200 root 1.1
1201 elmex 1.97 $r
1202 root 1.1 }
1203    
1204 elmex 1.92 sub skill_window {
1205 root 1.104 my $sw = new CFPlus::UI::ScrolledWindow (expand => 1);
1206 root 1.220
1207     $sw->add ($STATWIDS->{skill_tbl} = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1, .1, 0, 0, 1, .1]);
1208    
1209 elmex 1.97 $sw
1210 elmex 1.92 }
1211    
1212 root 1.48 sub formsep($) {
1213     scalar reverse join ",", unpack "(A3)*", reverse $_[0] * 1
1214 root 1.1 }
1215    
1216     my $METASERVER_ATIME;
1217    
1218     sub update_metaserver {
1219 elmex 1.81 my ($metaserver_dialog) = @_;
1220    
1221     $METASERVER = $metaserver_dialog
1222     if defined $metaserver_dialog;
1223    
1224 root 1.1 return if $METASERVER_ATIME > time;
1225     $METASERVER_ATIME = time + 60;
1226    
1227     my $table = $METASERVER->{table};
1228     $table->clear;
1229 root 1.178 $table->add_at (0, 0, my $label = new CFPlus::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
1230 root 1.1
1231 root 1.125 my $ok = 0;
1232 root 1.1
1233 root 1.125 CFPlus::background {
1234     my $ua = CFPlus::lwp_useragent;
1235 root 1.1
1236 root 1.125 CFPlus::background_msg CFPlus::from_json +(CFPlus::lwp_check $ua->get ($META_SERVER))->decoded_content;
1237     } sub {
1238     my ($msg) = @_;
1239     if ($msg) {
1240 root 1.1 $table->clear;
1241    
1242 root 1.62 my @tip = (
1243     "The current number of users logged in on the server.",
1244     "The hostname of the server.",
1245     "The time this server has been running without being restarted.",
1246     "The server software version - a '+' indicates a Crossfire+ server.",
1247     "Short information about this server provided by its admins.",
1248     );
1249     my @col = qw(#Users Host Uptime Version Description);
1250 root 1.178 $table->add_at ($_, 0, new CFPlus::UI::Label
1251 root 1.62 can_hover => 1, can_events => 1,
1252     align => 0, fg => [1, 1, 0],
1253     text => $col[$_], tooltip => $tip[$_])
1254     for 0 .. $#col;
1255 root 1.1
1256     my @align = qw(1 0 1 1 -1);
1257    
1258     my $y = 0;
1259 root 1.142 for my $m (@{ $msg->{servers} }) {
1260     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime, $highlight) =
1261     @$m{qw(ip age hostname users version description ibytes obytes uptime highlight)};
1262 root 1.1
1263     for ($desc) {
1264     s/<br>/\n/gi;
1265     s/<li>/\n· /gi;
1266     s/<.*?>//sgi;
1267 root 1.125 s/&amp;/&/g;
1268     s/&lt;/</g;
1269     s/&gt;/>/g;
1270 root 1.1 }
1271    
1272     $uptime = sprintf "%dd %02d:%02d:%02d",
1273 root 1.125 (int $uptime / 86400),
1274     (int $uptime / 3600) % 24,
1275     (int $uptime / 60) % 60,
1276     $uptime % 60;
1277 root 1.1
1278     $m = [$users, $host, $uptime, $version, $desc];
1279    
1280     $y++;
1281    
1282 root 1.178 $table->add_at (scalar @$m, $y, new CFPlus::UI::VBox children => [
1283 root 1.104 (new CFPlus::UI::Button
1284 root 1.62 text => "Use",
1285     tooltip => "Put this server into the <b>Host:Port</b> field",
1286     on_activate => sub {
1287 root 1.75 $HOST_ENTRY->set_text ($CFG->{profile}{default}{host} = $host);
1288 root 1.62 $METASERVER->hide;
1289 root 1.74 0
1290 root 1.62 },
1291     ),
1292 root 1.104 (new CFPlus::UI::Empty expand => 1),
1293 root 1.1 ]);
1294    
1295 root 1.178 $table->add_at ($_, $y, new CFPlus::UI::Label
1296 root 1.125 max_w => $::WIDTH * 0.4,
1297 root 1.62 ellipsise => 0,
1298     align => $align[$_],
1299     text => $m->[$_],
1300     tooltip => $tip[$_],
1301 root 1.142 fg => ($highlight ? [1, 1, 1] : [.7, .7, .7]),
1302 root 1.62 can_hover => 1,
1303     can_events => 1,
1304     fontsize => 0.8)
1305 root 1.1 for 0 .. $#$m;
1306     }
1307 root 1.125 } else {
1308     $ok or $label->set_text ("error while contacting metaserver");
1309 root 1.1 }
1310 root 1.125 };
1311    
1312 root 1.1 }
1313    
1314 root 1.40 sub metaserver_dialog {
1315 root 1.104 my $vbox = new CFPlus::UI::VBox;
1316     my $table = new CFPlus::UI::Table;
1317     $vbox->add (new CFPlus::UI::ScrolledWindow expand => 1, child => $table);
1318 elmex 1.81
1319 root 1.114 my $dialog = new CFPlus::UI::Toplevel
1320 root 1.62 title => "Server List",
1321     name => 'metaserver_dialog',
1322     x => 'center',
1323     y => 'center',
1324     z => 3,
1325 root 1.125 force_w => $::WIDTH * 0.9,
1326     force_h => $::HEIGHT * 0.7,
1327 elmex 1.81 child => $vbox,
1328 root 1.80 has_close_button => 1,
1329 elmex 1.81 table => $table,
1330 root 1.40 on_visibility_change => sub {
1331 elmex 1.81 update_metaserver ($_[0]) if $_[1];
1332 root 1.74 0
1333 root 1.40 },
1334     ;
1335    
1336     $dialog
1337     }
1338    
1339 root 1.1 sub server_setup {
1340 root 1.104 my $vbox = new CFPlus::UI::VBox;
1341 elmex 1.19
1342 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1343     label => "Connection Settings",
1344     child => (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]),
1345     );
1346 root 1.178 $table->add_at (0, 2, new CFPlus::UI::Label valign => 0, align => 1, text => "Host:Port");
1347 root 1.1
1348     {
1349 root 1.178 $table->add_at (1, 2, my $vbox = new CFPlus::UI::VBox);
1350 root 1.1
1351     $vbox->add (
1352 root 1.104 $HOST_ENTRY = new CFPlus::UI::Entry
1353 root 1.1 expand => 1,
1354 root 1.75 text => $CFG->{profile}{default}{host},
1355 root 1.1 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1356 root 1.18 on_changed => sub {
1357 root 1.1 my ($self, $value) = @_;
1358 root 1.75 $CFG->{profile}{default}{host} = $value;
1359 root 1.192 1
1360 root 1.1 }
1361     );
1362    
1363 root 1.104 $vbox->add (new CFPlus::UI::Button
1364 root 1.40 expand => 1,
1365     text => "Server List",
1366     other => $METASERVER,
1367 root 1.1 tooltip => "Show a list of available crossfire servers",
1368 root 1.74 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1369 root 1.192 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1370 root 1.1 );
1371     }
1372    
1373 root 1.178 $table->add_at (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
1374     $table->add_at (1, 4, new CFPlus::UI::Entry
1375 root 1.75 text => $CFG->{profile}{default}{user},
1376 root 1.1 tooltip => "The name of your character on the server",
1377 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1378 root 1.1 );
1379    
1380 root 1.178 $table->add_at (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
1381     $table->add_at (1, 5, new CFPlus::UI::Entry
1382 root 1.75 text => $CFG->{profile}{default}{password},
1383 root 1.1 hidden => 1,
1384     tooltip => "The password for your character",
1385 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1386 root 1.1 );
1387    
1388 root 1.178 $table->add_at (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1389     $table->add_at (1, 7, new CFPlus::UI::Slider
1390 root 1.30 force_w => 100,
1391 root 1.1 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1392     tooltip => "This is the size of the portion of the map update the server sends you. "
1393     . "If you set this to a high value you will be able to see further, "
1394     . "but you also increase bandwidth requirements and latency. "
1395     . "This option is only used once at log-in.",
1396 root 1.192 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1397 root 1.1 );
1398    
1399 root 1.189 $table->add_at (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
1400     $table->add_at (1, 8, new CFPlus::UI::Entry
1401 root 1.143 text => $CFG->{output_rate},
1402 root 1.192 tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1403     . "when sending data. When 0 or unset, the server "
1404     . "default will be used, which is usually around 100kb/s. Most servers will "
1405     . "dynamically find an optimal rate, so adjust this only when necessary.",
1406     on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1407 root 1.143 );
1408    
1409 root 1.189 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1410     $table->add_at (1, 9, new CFPlus::UI::Entry
1411 root 1.1 text => $CFG->{output_count},
1412     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1413 root 1.192 on_changed => sub { $CFG->{output_count} = $_[1]; 1 },
1414 root 1.1 );
1415    
1416 root 1.189 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1417     $table->add_at (1, 10, new CFPlus::UI::Entry
1418 root 1.1 text => $CFG->{output_sync},
1419     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1420 root 1.192 on_changed => sub { $CFG->{output_sync} = $_[1]; 1 },
1421 root 1.1 );
1422    
1423 root 1.189 $table->add_at (1, 11, $LOGIN_BUTTON = new CFPlus::UI::Button
1424 root 1.1 expand => 1,
1425     align => 0,
1426     text => "Login",
1427 root 1.18 on_activate => sub {
1428 root 1.1 $CONN ? stop_game
1429     : start_game;
1430 root 1.192 1
1431 root 1.1 },
1432     );
1433    
1434 root 1.149 $vbox->add (new CFPlus::UI::FancyFrame
1435     label => "Server Info",
1436     child => ($SERVER_INFO = new CFPlus::UI::Label ellipsise => 0),
1437     );
1438    
1439     $vbox
1440     }
1441    
1442     sub client_setup {
1443     my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1];
1444    
1445     my $row = 0;
1446    
1447 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Tip of the day");
1448     $table->add_at (1, $row++, new CFPlus::UI::CheckBox
1449 root 1.119 state => $CFG->{show_tips},
1450     tooltip => "Show the <b>Tip of the day</b> window at startup?",
1451     on_changed => sub {
1452     my ($self, $value) = @_;
1453 root 1.121 $CFG->{show_tips} = $value;
1454 root 1.119 0
1455     }
1456     );
1457    
1458 root 1.178 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Messages Window Size");
1459     $table->add_at (1, $row++, my $saycmd = new CFPlus::UI::Entry
1460 root 1.149 text => $CFG->{logview_max_par},
1461     tooltip => "This is maximum number of messages remembered in the <b>Messages</b> window. If the server "
1462     . "sends more messages than this number, older messages get removed to save memory and "
1463     . "computing time. A value of <b>0</b> disables this feature, but that is not recommended.",
1464     on_changed => sub {
1465     my ($self, $value) = @_;
1466 elmex 1.182 $MESSAGE_WINDOW->set_max_para ($CFG->{logview_max_par} = $value*1);
1467 root 1.149 0
1468     },
1469 root 1.67 );
1470    
1471 root 1.149 $table
1472 root 1.1 }
1473    
1474 root 1.49 sub autopickup_setup {
1475 root 1.170 my $r = new CFPlus::UI::ScrolledWindow (
1476     expand => 1,
1477     scroll_y => 1
1478     );
1479     $r->add (my $table = new CFPlus::UI::Table
1480     row_expand => [0],
1481     col_expand => [0, 1, 0, 1],
1482     );
1483 elmex 1.44
1484 elmex 1.43 for (
1485 root 1.51 ["General", 0, 0,
1486 root 1.86 ["Enable autopickup" => PICKUP_NEWMODE, \$PICKUP_ENABLE],
1487 root 1.58 ["Inhibit autopickup" => PICKUP_INHIBIT],
1488     ["Stop before pickup" => PICKUP_STOP],
1489     ["Debug autopickup" => PICKUP_DEBUG],
1490 root 1.51 ],
1491     ["Weapons", 0, 6,
1492 root 1.58 ["All weapons" => PICKUP_ALLWEAPON],
1493     ["Missile weapons" => PICKUP_MISSILEWEAPON],
1494     ["Bows" => PICKUP_BOW],
1495     ["Arrows" => PICKUP_ARROW],
1496 root 1.51 ],
1497     ["Armour", 0, 12,
1498 root 1.58 ["Helmets" => PICKUP_HELMET],
1499     ["Shields" => PICKUP_SHIELD],
1500     ["Body Armour" => PICKUP_ARMOUR],
1501     ["Boots" => PICKUP_BOOTS],
1502     ["Gloves" => PICKUP_GLOVES],
1503     ["Cloaks" => PICKUP_CLOAK],
1504 root 1.51 ],
1505    
1506 root 1.133 ["Readables", 2, 0,
1507 root 1.58 ["Spellbooks" => PICKUP_SPELLBOOK],
1508     ["Skillscrolls" => PICKUP_SKILLSCROLL],
1509     ["Normal Books/Scrolls" => PICKUP_READABLES],
1510 root 1.51 ],
1511 root 1.133 ["Misc", 2, 5,
1512 root 1.58 ["Food" => PICKUP_FOOD],
1513     ["Drinks" => PICKUP_DRINK],
1514     ["Valuables (Money, Gems)" => PICKUP_VALUABLES],
1515     ["Keys" => PICKUP_KEY],
1516     ["Magical Items" => PICKUP_MAGICAL],
1517     ["Potions" => PICKUP_POTION],
1518     ["Magic Devices" => PICKUP_MAGIC_DEVICE],
1519     ["Ignore cursed" => PICKUP_NOT_CURSED],
1520     ["Jewelery" => PICKUP_JEWELS],
1521 root 1.133 ["Flesh" => PICKUP_FLESH],
1522 root 1.51 ],
1523 elmex 1.66 ["Weight/Value ratio", 2, 17]
1524 elmex 1.43 )
1525     {
1526 root 1.51 my ($title, $x, $y, @bits) = @$_;
1527 root 1.178 $table->add_at ($x, $y, new CFPlus::UI::Label text => $title, align => 1, fg => [1, 1, 0]);
1528 root 1.51
1529     for (@bits) {
1530     ++$y;
1531    
1532 elmex 1.43 my $mask = $_->[1];
1533 root 1.178 $table->add_at ($x , $y, new CFPlus::UI::Label text => $_->[0], align => 1, expand => 1);
1534     $table->add_at ($x+1, $y, my $checkbox = new CFPlus::UI::CheckBox
1535 elmex 1.83 state => $::CFG->{pickup} & $mask,
1536 elmex 1.43 on_changed => sub {
1537     my ($box, $value) = @_;
1538 root 1.63
1539 elmex 1.43 if ($value) {
1540 elmex 1.45 $::CFG->{pickup} |= $mask;
1541 elmex 1.43 } else {
1542 root 1.63 $::CFG->{pickup} &= ~$mask;
1543 elmex 1.43 }
1544 root 1.63
1545     $::CONN->send_command ("pickup $::CFG->{pickup}")
1546 elmex 1.45 if defined $::CONN;
1547 root 1.74
1548     0
1549 elmex 1.43 });
1550 root 1.86
1551     ${$_->[2]} = $checkbox if $_->[2];
1552 elmex 1.43 }
1553     }
1554    
1555 root 1.178 $table->add_at (2, 18, new CFPlus::UI::ValSlider
1556 elmex 1.83 range => [$::CFG->{pickup} & 0xF, 0, 16, 1, 1],
1557     template => ">= 99",
1558 elmex 1.66 to_value => sub { ">= " . 5 * $_[0] },
1559     on_changed => sub {
1560     my ($slider, $value) = @_;
1561    
1562 elmex 1.83 $::CFG->{pickup} &= ~0xF;
1563 elmex 1.66 $::CFG->{pickup} |= int $value
1564     if $value;
1565     1;
1566     });
1567 elmex 1.83
1568 root 1.178 $table->add_at (3, 18, new CFPlus::UI::Button
1569 elmex 1.66 text => "set",
1570     on_activate => sub {
1571     $::CONN->send_command ("pickup $::CFG->{pickup}")
1572     if defined $::CONN;
1573 root 1.74 0
1574 elmex 1.66 });
1575    
1576 root 1.170 $r
1577 elmex 1.43 }
1578    
1579 root 1.102 my %SORT_ORDER = (
1580     type => undef,
1581 root 1.130 mtime => sub {
1582     my $NOW = time;
1583     sort {
1584     my $atime = $a->{mtime} - $NOW; $atime = $atime < 5 * 60 ? int $atime / 60 : 6;
1585     my $btime = $b->{mtime} - $NOW; $btime = $btime < 5 * 60 ? int $btime / 60 : 6;
1586    
1587     ($a->{flags} & F_LOCKED) <=> ($b->{flags} & F_LOCKED)
1588     or $btime <=> $atime
1589     or $a->{type} <=> $b->{type}
1590     } @_
1591     },
1592 root 1.102 weight => sub { sort {
1593     $a->{weight} * ($a->{nrof} || 1) <=> $b->{weight} * ($b->{nrof} || 1)
1594     or $a->{type} <=> $b->{type}
1595     } @_ },
1596     );
1597    
1598 elmex 1.85 sub inventory_widget {
1599 root 1.104 my $hb = new CFPlus::UI::HBox homogeneous => 1;
1600 root 1.1
1601 root 1.104 $hb->add (my $vb1 = new CFPlus::UI::VBox);
1602     $vb1->add (new CFPlus::UI::Label align => 0, text => "Player");
1603 root 1.99
1604 root 1.104 $vb1->add (my $hb1 = new CFPlus::UI::HBox);
1605 root 1.99
1606     use sort 'stable';
1607    
1608 root 1.104 $hb1->add (new CFPlus::UI::Selector
1609 root 1.102 value => $::CFG->{inv_sort},
1610 root 1.99 options => [
1611 root 1.102 [type => "Type/Name"],
1612     [mtime => "Recent/Normal/Locked"],
1613     [weight => "Weight/Type"],
1614 root 1.99 ],
1615     on_changed => sub {
1616 root 1.102 $::CFG->{inv_sort} = $_[1];
1617     $INV->set_sort_order ($SORT_ORDER{$_[1]});
1618 root 1.99 },
1619     );
1620 root 1.104 $hb1->add (new CFPlus::UI::Label text => "Weight: ", align => 1, expand => 1);
1621 root 1.99 #TODO# update to weigh/maxweight
1622 root 1.104 $hb1->add ($STATWIDS->{i_weight} = new CFPlus::UI::Label align => -1);
1623 root 1.99
1624 root 1.104 $vb1->add (my $sw1 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1625     $sw1->add ($INV = new CFPlus::UI::Inventory);
1626 root 1.127 $INV->set_sort_order ($SORT_ORDER{$::CFG->{inv_sort}});
1627 root 1.1
1628 root 1.104 $hb->add (my $vb2 = new CFPlus::UI::VBox);
1629 elmex 1.17
1630 root 1.215 $vb2->add ($INVR_HB = new CFPlus::UI::HBox);
1631 elmex 1.14
1632 root 1.104 $vb2->add (my $sw2 = new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
1633     $sw2->add ($INVR = new CFPlus::UI::Inventory);
1634 root 1.1
1635 elmex 1.27 # XXX: Call after $INVR = ... because set_opencont sets the items
1636 root 1.104 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1637 elmex 1.27
1638 elmex 1.85 $hb
1639 root 1.1 }
1640    
1641 root 1.200 sub media_window {
1642     my $vb = new CFPlus::UI::VBox;
1643    
1644     $vb->add (new CFPlus::UI::FancyFrame
1645     label => "Currently playing music",
1646 root 1.201 child => new CFPlus::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1647     child => ($MUSIC_PLAYING_WIDGET = new CFPlus::UI::Label ellipsise => 0, fontsize => 0.8),
1648 root 1.200 );
1649    
1650     $vb->add (new CFPlus::UI::FancyFrame
1651 root 1.201 label => "Other media used in this session",
1652     expand => 1,
1653     child => ($LICENSE_WIDGET = new CFPlus::UI::TextScroller
1654 root 1.200 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1655     );
1656    
1657     $vb
1658     }
1659    
1660 root 1.201 sub add_license {
1661     my ($meta) = @_;
1662    
1663 root 1.219 $meta = $meta->{data}
1664 root 1.201 or return;
1665    
1666     $meta->{license} || $meta->{author} || $meta->{source}
1667     or return;
1668    
1669     $LICENSE_WIDGET->add_paragraph ({
1670     fg => [1, 1, 1, 1],
1671     markup => "<small>"
1672     . "<b>Name:</b> " . (CFPlus::asxml $meta->{name}) . "\n"
1673     . "<b>Author:</b> " . (CFPlus::asxml $meta->{author}) . "\n"
1674     . "<b>Source:</b> " . (CFPlus::asxml $meta->{source}) . "\n"
1675     . "<b>License:</b> " . (CFPlus::asxml $meta->{license}) . "\n"
1676     . "</small>",
1677     });
1678     $LICENSE_WIDGET->scroll_to_bottom;
1679     }
1680    
1681 root 1.86 sub toggle_player_page {
1682     my ($widget) = @_;
1683    
1684     if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1685     $PL_WINDOW->hide;
1686     } else {
1687     $PL_NOTEBOOK->set_current_page ($widget);
1688     $PL_WINDOW->show;
1689     }
1690     }
1691    
1692 elmex 1.85 sub player_window {
1693 root 1.114 my $plwin = $PL_WINDOW = new CFPlus::UI::Toplevel
1694 elmex 1.85 x => "center",
1695     y => "center",
1696     force_w => $WIDTH * 9/10,
1697     force_h => $HEIGHT * 9/10,
1698     title => "Player",
1699 elmex 1.90 name => "playerbook",
1700 elmex 1.85 has_close_button => 1
1701     ;
1702    
1703     my $ntb =
1704     $PL_NOTEBOOK =
1705 root 1.157 new CFPlus::UI::Notebook expand => 1;
1706 root 1.86
1707 root 1.184 $ntb->add_tab (
1708 root 1.95 "Statistics (F2)" => $STATS_PAGE = stats_window,
1709 elmex 1.92 "Shows statistics, where all your Stats and Resistances are shown."
1710     );
1711 root 1.184 $ntb->add_tab (
1712 root 1.95 "Skills (F3)" => $SKILL_PAGE = skill_window,
1713 elmex 1.92 "Shows all your Skills."
1714 elmex 1.85 );
1715 elmex 1.97
1716 root 1.131 my $spellsw = $SPELL_PAGE = new CFPlus::UI::ScrolledWindow (expand => 1, scroll_y => 1);
1717     $spellsw->add ($SPELL_LIST = new CFPlus::UI::SpellList);
1718 root 1.184 $ntb->add_tab (
1719 elmex 1.97 "Spellbook (F4)" => $spellsw,
1720 root 1.86 "Displays all spells you have and lets you edit keyboard shortcuts for them."
1721 elmex 1.85 );
1722 root 1.184 $ntb->add_tab (
1723 root 1.95 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1724 root 1.86 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1725     . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1726 elmex 1.85 );
1727 root 1.200 $ntb->add_tab (Pickup => autopickup_setup,
1728 root 1.155 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1729 elmex 1.85
1730 root 1.200 $ntb->add_tab (Media => media_window,
1731     "License, Author and Source info for media sent by the server.");
1732    
1733 root 1.88 $ntb->set_current_page ($INVENTORY_PAGE);
1734 root 1.86
1735 elmex 1.85 $plwin->add ($ntb);
1736     $plwin
1737 elmex 1.38 }
1738    
1739 root 1.49 sub keyboard_setup {
1740 root 1.137 CFPlus::Macro::keyboard_setup
1741 elmex 1.24 }
1742    
1743 root 1.64 sub help_window {
1744 root 1.114 my $win = new CFPlus::UI::Toplevel
1745 root 1.41 x => 'center',
1746     y => 'center',
1747 root 1.119 z => 4,
1748 root 1.41 name => 'doc_browser',
1749     force_w => int $WIDTH * 7/8,
1750     force_h => int $HEIGHT * 7/8,
1751 root 1.87 title => "Help Browser",
1752     has_close_button => 1;
1753 root 1.1
1754 root 1.104 $win->add (my $vbox = new CFPlus::UI::VBox);
1755 root 1.1
1756 root 1.114 $vbox->add (new CFPlus::UI::FancyFrame
1757     label => "Navigation",
1758     child => (my $buttons = new CFPlus::UI::HBox),
1759     );
1760 root 1.104 $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1761 root 1.114 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1762 root 1.1
1763 root 1.111 my @history;
1764     my @future;
1765 root 1.112 my $curnode;
1766 root 1.111
1767     my $load_node; $load_node = sub {
1768 root 1.115 my ($node, $para) = @_;
1769 root 1.111
1770     $buttons->clear;
1771    
1772 root 1.116 $buttons->add (new CFPlus::UI::Button
1773     text => "⇤",
1774     tooltip => "back to the starting page",
1775     on_activate => sub {
1776     unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1777     unshift @future, @history;
1778     @history = ();
1779     $load_node->(@{shift @future});
1780     },
1781     );
1782    
1783 root 1.111 if (@history) {
1784 root 1.112 $buttons->add (new CFPlus::UI::Button
1785 root 1.113 text => "⋘",
1786 root 1.116 tooltip => "back to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $history[-1][0]) . "</i>",
1787 root 1.112 on_activate => sub {
1788 root 1.115 unshift @future, [$curnode, $viewer->current_paragraph] if $curnode;
1789     $load_node->(@{pop @history});
1790 root 1.113 },
1791     );
1792 root 1.112 }
1793 root 1.111
1794 root 1.112 if (@future) {
1795     $buttons->add (new CFPlus::UI::Button
1796 root 1.113 text => "â‹™",
1797 root 1.116 tooltip => "forward to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $future[0][0]) . "</i>",
1798 root 1.112 on_activate => sub {
1799 root 1.115 push @history, [$curnode, $viewer->current_paragraph];
1800     $load_node->(@{shift @future});
1801 root 1.113 },
1802     );
1803 root 1.111 }
1804 root 1.112
1805 root 1.113 $buttons->add (new CFPlus::UI::Label text => " ");
1806    
1807     my @path = CFPlus::Pod::full_path_of $node;
1808     pop @path; # drop current node
1809    
1810     for my $node (@path) {
1811     $buttons->add (new CFPlus::UI::Button
1812     text => $node->{kw}[0],
1813     tooltip => "go to <i>" . (CFPlus::asxml CFPlus::Pod::full_path $node) . "</i>",
1814     on_activate => sub {
1815 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1816 root 1.113 $load_node->($node);
1817     },
1818     );
1819     $buttons->add (new CFPlus::UI::Label text => "/");
1820     }
1821    
1822 root 1.114 $buttons->add (new CFPlus::UI::Label text => $node->{kw}[0], padding_x => 4, padding_y => 4);
1823 root 1.113
1824 root 1.112 $curnode = $node;
1825    
1826 root 1.111 $viewer->clear;
1827 root 1.112 $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $curnode);
1828 root 1.115 $viewer->scroll_to ($para);
1829 root 1.111 };
1830    
1831     $load_node->(CFPlus::Pod::find pod => "mainpage");
1832    
1833 root 1.113 $CFPlus::Pod::goto_document = sub {
1834 root 1.111 my (@path) = @_;
1835    
1836 root 1.115 push @history, [$curnode, $viewer->current_paragraph] if $curnode; @future = ();
1837 root 1.111
1838 root 1.116 $load_node->((CFPlus::Pod::find @path)[0]);
1839 root 1.111 $win->show;
1840     };
1841 root 1.1
1842     $win
1843     }
1844    
1845 root 1.119 sub open_string_query {
1846     my ($title, $cb, $txt, $tooltip) = @_;
1847     my $dialog = new CFPlus::UI::Toplevel
1848     x => "center",
1849     y => "center",
1850     z => 50,
1851     force_w => $WIDTH * 4/5,
1852     title => $title;
1853    
1854     $dialog->add (
1855     my $e = new CFPlus::UI::Entry
1856     on_activate => sub { $cb->(@_); $dialog->hide; 0 },
1857     on_key_down => sub { $_[1]->{sym} == 27 and $dialog->hide; 0 },
1858     tooltip => $tooltip
1859     );
1860    
1861     $e->grab_focus;
1862     $e->set_text ($txt) if $txt;
1863     $dialog->show;
1864     }
1865    
1866     sub open_quit_dialog {
1867     unless ($QUIT_DIALOG) {
1868     $QUIT_DIALOG = new CFPlus::UI::Toplevel
1869     x => "center",
1870     y => "center",
1871     z => 50,
1872     title => "Really Quit?",
1873     on_key_down => sub {
1874     my ($dialog, $ev) = @_;
1875     $ev->{sym} == 27 and $dialog->hide;
1876     }
1877     ;
1878    
1879     $QUIT_DIALOG->add (my $vb = new CFPlus::UI::VBox expand => 1);
1880    
1881     $vb->add (new CFPlus::UI::Label
1882     text => "You should find a savebed and apply it first!",
1883     max_w => $WIDTH * 0.25,
1884     ellipsize => 0,
1885     );
1886     $vb->add (my $hb = new CFPlus::UI::HBox expand => 1);
1887     $hb->add (new CFPlus::UI::Button
1888     text => "Ok",
1889     expand => 1,
1890     on_activate => sub { $QUIT_DIALOG->hide; 0 },
1891     );
1892     $hb->add (new CFPlus::UI::Button
1893     text => "Quit anyway",
1894     expand => 1,
1895 root 1.213 on_activate => sub { Event::unloop_all },
1896 root 1.119 );
1897     }
1898    
1899     $QUIT_DIALOG->show;
1900     $QUIT_DIALOG->grab_focus;
1901     }
1902    
1903     sub show_tip_of_the_day {
1904     # find all tips
1905     my @tod = CFPlus::Pod::find tip_of_the_day => "*";
1906    
1907 root 1.148 CFPlus::DB::get state => "tip_of_the_day", sub {
1908     my ($todindex) = @_;
1909     $todindex = 0 if $todindex >= @tod;
1910     CFPlus::DB::put state => tip_of_the_day => $todindex + 1, sub { };
1911    
1912     # create dialog
1913     my $dialog;
1914    
1915     my $close = sub {
1916     $dialog->destroy;
1917     };
1918    
1919     $dialog = new CFPlus::UI::Toplevel
1920     x => "center",
1921     y => "center",
1922     z => 3,
1923     name => 'tip_of_the_day',
1924     force_w => int $WIDTH * 4/9,
1925     force_h => int $WIDTH * 2/9,
1926     title => "Tip of the day #" . (1 + $todindex),
1927     child => my $vbox = new CFPlus::UI::VBox,
1928     has_close_button => 1,
1929     on_delete => $close,
1930     ;
1931    
1932     $vbox->add (my $viewer = new CFPlus::UI::TextScroller
1933     expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4);
1934     $viewer->add_paragraph (CFPlus::Pod::as_paragraphs CFPlus::Pod::section_of $tod[$todindex]);
1935    
1936     $vbox->add (my $table = new CFPlus::UI::Table col_expand => [0, 1]);
1937    
1938 root 1.178 $table->add_at (0, 0, new CFPlus::UI::Button
1939 root 1.148 text => "Close",
1940     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>.",
1941     on_activate => $close,
1942     );
1943 root 1.119
1944 root 1.178 $table->add_at (2, 0, new CFPlus::UI::Button
1945 root 1.148 text => "Next",
1946     tooltip => "Show the next <b>Tip of the day</b>.",
1947     on_activate => sub {
1948     $close->();
1949     &show_tip_of_the_day;
1950     },
1951     );
1952 root 1.119
1953 root 1.148 $dialog->show;
1954 root 1.119 };
1955     }
1956    
1957 root 1.1 sub sdl_init {
1958 root 1.104 CFPlus::SDL_Init
1959 root 1.1 and die "SDL::Init failed!\n";
1960     }
1961    
1962     sub video_init {
1963     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1964    
1965     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1966    
1967 root 1.172 ($WIDTH, $HEIGHT, my ($rgb, $alpha)) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1968 root 1.1 $FULLSCREEN = $CFG->{fullscreen};
1969     $FAST = $CFG->{fast};
1970    
1971 root 1.172 CFPlus::SDL_SetVideoMode $WIDTH, $HEIGHT, $rgb, $alpha, $FULLSCREEN
1972 root 1.104 or die "SDL_SetVideoMode failed: " . (CFPlus::SDL_GetError) . "\n";
1973 root 1.1
1974     $SDL_ACTIVE = 1;
1975     $LAST_REFRESH = time - 0.01;
1976    
1977 root 1.104 CFPlus::OpenGL::init;
1978 root 1.1
1979     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1980    
1981 root 1.104 $CFPlus::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1982 root 1.1
1983     #############################################################################
1984    
1985     if ($DEBUG_STATUS) {
1986 root 1.104 CFPlus::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1987 root 1.1 } else {
1988     # create the widgets
1989    
1990 root 1.104 $DEBUG_STATUS = new CFPlus::UI::Label
1991 root 1.30 padding => 0,
1992     z => 100,
1993     force_x => "max",
1994     force_y => 0;
1995 root 1.1 $DEBUG_STATUS->show;
1996 elmex 1.34
1997 root 1.104 $STATUSBOX = new CFPlus::UI::Statusbox;
1998 root 1.54 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", timeout => 864000, pri => -100, color => [1, 1, 1, 0.8]);
1999 root 1.1
2000 root 1.104 (new CFPlus::UI::Frame
2001 root 1.1 bg => [0, 0, 0, 0.4],
2002 root 1.30 force_x => 0,
2003     force_y => "max",
2004 root 1.1 child => $STATUSBOX,
2005     )->show;
2006    
2007 root 1.114 CFPlus::UI::Toplevel->new (
2008 root 1.47 title => "Map",
2009 root 1.42 name => "mapmap",
2010 root 1.30 x => 0,
2011     y => $FONTSIZE + 8,
2012 root 1.1 border_bg => [1, 1, 1, 192/255],
2013     bg => [1, 1, 1, 0],
2014 root 1.104 child => ($MAPMAP = new CFPlus::MapWidget::MapMap
2015 root 1.1 tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
2016     ),
2017     )->show;
2018    
2019 root 1.104 $MAPWIDGET = new CFPlus::MapWidget;
2020 root 1.1 $MAPWIDGET->connect (activate_console => sub {
2021     my ($mapwidget, $preset) = @_;
2022    
2023 elmex 1.182 $MESSAGE_WINDOW->activate_console ($preset)
2024     if $MESSAGE_WINDOW;
2025 root 1.1 });
2026     $MAPWIDGET->show;
2027 root 1.74 $MAPWIDGET->grab_focus;
2028 root 1.1
2029 root 1.114 $SETUP_DIALOG = new CFPlus::UI::Toplevel
2030 root 1.49 title => "Setup",
2031     name => "setup_dialog",
2032     x => 'center',
2033     y => 'center',
2034 root 1.53 z => 2,
2035 root 1.49 force_w => $::WIDTH * 0.6,
2036     force_h => $::HEIGHT * 0.6,
2037 root 1.74 has_close_button => 1,
2038 root 1.49 ;
2039    
2040 elmex 1.81 $METASERVER = metaserver_dialog;
2041 elmex 1.182 $MESSAGE_WINDOW = new CFPlus::UI::MessageWindow;
2042 elmex 1.81
2043 root 1.104 $SETUP_DIALOG->add ($SETUP_NOTEBOOK = new CFPlus::UI::Notebook expand => 1, debug => 1,
2044     filter => new CFPlus::UI::ScrolledWindow expand => 1, scroll_y => 1);
2045 root 1.49
2046 root 1.184 $SETUP_NOTEBOOK->add_tab (Server => $SETUP_SERVER = server_setup,
2047 root 1.49 "Configure the server to play on, your username, password and other server-related options.");
2048 root 1.184 $SETUP_NOTEBOOK->add_tab (Client => client_setup,
2049 root 1.149 "Configure various client-specific settings.");
2050 root 1.184 $SETUP_NOTEBOOK->add_tab (Graphics => graphics_setup,
2051 root 1.49 "Configure the video mode, performance, fonts and other graphical aspects of the game.");
2052 root 1.184 $SETUP_NOTEBOOK->add_tab (Audio => audio_setup,
2053 root 1.49 "Configure the use of audio, sound effects and background music.");
2054 root 1.184 $SETUP_NOTEBOOK->add_tab (Keyboard => $SETUP_KEYBOARD = keyboard_setup,
2055 root 1.75 "Lets you define, edit and delete key bindings."
2056     . "There is a shortcut for making bindings: <b>Control-Insert</b> opens the binding editor "
2057 root 1.49 . "with nothing set and the recording started. After doing the actions you "
2058 root 1.54 . "want to record press <b>Insert</b> and you will be asked to press a key-combo. "
2059 root 1.49 . "After pressing the combo the binding will be saved automatically and the "
2060     . "binding editor closes");
2061 root 1.184 $SETUP_NOTEBOOK->add_tab (Debug => debug_setup,
2062 root 1.75 "Some debuggin' options. Do not ask.");
2063 root 1.49
2064 root 1.104 $BUTTONBAR = new CFPlus::UI::Buttonbar x => 0, y => 0, z => 200; # put on top
2065 root 1.1
2066 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Setup", other => $SETUP_DIALOG,
2067 root 1.49 tooltip => "Toggles a dialog where you can configure all aspects of this client.");
2068    
2069 elmex 1.182 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Message Window", other => $MESSAGE_WINDOW,
2070 root 1.1 tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
2071    
2072     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
2073    
2074 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Playerbook", other => player_window,
2075 elmex 1.85 tooltip => "Toggles the player view, where you can manage Inventory, Spells, Skills and see your Stats.");
2076 root 1.1
2077 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
2078 root 1.1 text => "Save Config",
2079     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
2080 root 1.18 on_activate => sub {
2081 root 1.104 $::CFG->{layout} = CFPlus::UI::get_layout;
2082     CFPlus::write_cfg "$Crossfire::VARDIR/cfplusrc";
2083 root 1.1 status "Configuration Saved";
2084 root 1.74 0
2085 root 1.1 },
2086     );
2087    
2088 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Flopper text => "Help!", other => $HELP_WINDOW = help_window,
2089 root 1.1 tooltip => "View Documentation");
2090    
2091 elmex 1.134
2092 root 1.104 $BUTTONBAR->add (new CFPlus::UI::Button
2093 root 1.18 text => "Quit",
2094     tooltip => "Terminates the program",
2095     on_activate => sub {
2096 root 1.1 if ($CONN) {
2097     open_quit_dialog;
2098     } else {
2099 root 1.213 Event::unloop_all;
2100 root 1.1 }
2101 root 1.74 0
2102 root 1.1 },
2103     );
2104    
2105     $BUTTONBAR->show;
2106 root 1.49 $SETUP_DIALOG->show;
2107 elmex 1.214 $MESSAGE_WINDOW->show;
2108 root 1.49 }
2109 root 1.1
2110 root 1.49 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
2111 root 1.1 }
2112    
2113     sub video_shutdown {
2114 root 1.104 CFPlus::OpenGL::shutdown;
2115 root 1.73
2116 root 1.1 undef $SDL_ACTIVE;
2117     }
2118    
2119     my %animate_object;
2120     my $animate_timer;
2121    
2122     my $fps = 9;
2123    
2124     sub force_refresh {
2125 root 1.208 if ($ENV{CFPLUS_DEBUG} & 4) {
2126     $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
2127     debug sprintf "%3.2f", $fps;
2128     }
2129 root 1.1
2130 root 1.104 $CFPlus::UI::ROOT->draw;
2131 root 1.208 CFPlus::SDL_GL_SwapBuffers;
2132 root 1.1 $LAST_REFRESH = $NOW;
2133 root 1.208 $WANT_REFRESH->stop;
2134     }
2135 root 1.1
2136 root 1.208 $WANT_REFRESH = Event->idle (min => 0.001, max => 0.06, parked => 1, cb => \&force_refresh);
2137 root 1.1
2138 root 1.208 my $input = Event->timer (after => 0, hard => 0, interval => 1 / 50, cb => sub {
2139 root 1.1 $NOW = time;
2140    
2141     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
2142 root 1.169 for CFPlus::poll_events;
2143 root 1.1
2144     if (%animate_object) {
2145     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
2146 root 1.208 $WANT_REFRESH->start;
2147 root 1.1 }
2148     });
2149    
2150     sub animation_start {
2151     my ($widget) = @_;
2152     $animate_object{$widget} = $widget;
2153     }
2154    
2155     sub animation_stop {
2156     my ($widget) = @_;
2157     delete $animate_object{$widget};
2158     }
2159    
2160     %SDL_CB = (
2161 root 1.104 CFPlus::SDL_QUIT => sub {
2162 root 1.151 exit;
2163 root 1.1 },
2164 root 1.104 CFPlus::SDL_VIDEORESIZE => sub {
2165 root 1.1 },
2166 root 1.104 CFPlus::SDL_VIDEOEXPOSE => sub {
2167     CFPlus::UI::full_refresh;
2168 root 1.1 },
2169 root 1.104 CFPlus::SDL_ACTIVEEVENT => sub {
2170 root 1.137 # not useful, as APPACTIVE include sonly iconified state, not unmapped
2171 root 1.171 # printf "active %x %x %x\n", $_[0]{gain}, $_[0]{state}, CFPlus::SDL_GetAppState;#d#
2172     # printf "a %x\n", CFPlus::SDL_GetAppState & CFPlus::SDL_APPACTIVE;#d#
2173 root 1.137 # printf "A\n" if $_[0]{state} & CFPlus::SDL_APPACTIVE;
2174     # printf "K\n" if $_[0]{state} & CFPlus::SDL_APPINPUTFOCUS;
2175     # printf "M\n" if $_[0]{state} & CFPlus::SDL_APPMOUSEFOCUS;
2176 root 1.1 },
2177 root 1.104 CFPlus::SDL_KEYDOWN => sub {
2178     if ($_[0]{mod} & CFPlus::KMOD_ALT && $_[0]{sym} == 13) {
2179 root 1.1 # alt-enter
2180 root 1.94 $FULLSCREEN_ENABLE->toggle;
2181 root 1.1 video_shutdown;
2182     video_init;
2183     } else {
2184 root 1.104 CFPlus::UI::feed_sdl_key_down_event ($_[0]);
2185 root 1.1 }
2186     },
2187 root 1.104 CFPlus::SDL_KEYUP => \&CFPlus::UI::feed_sdl_key_up_event,
2188     CFPlus::SDL_MOUSEMOTION => \&CFPlus::UI::feed_sdl_motion_event,
2189     CFPlus::SDL_MOUSEBUTTONDOWN => \&CFPlus::UI::feed_sdl_button_down_event,
2190     CFPlus::SDL_MOUSEBUTTONUP => \&CFPlus::UI::feed_sdl_button_up_event,
2191     CFPlus::SDL_USEREVENT => sub {
2192 root 1.1 if ($_[0]{code} == 1) {
2193     audio_channel_finished $_[0]{data1};
2194     } elsif ($_[0]{code} == 0) {
2195     audio_music_finished;
2196     }
2197     },
2198     );
2199    
2200     #############################################################################
2201    
2202     $SIG{INT} = $SIG{TERM} = sub { exit };
2203    
2204     {
2205 root 1.104 CFPlus::read_cfg "$Crossfire::VARDIR/cfplusrc";
2206 root 1.147 CFPlus::DB::Server::run;
2207    
2208 root 1.104 CFPlus::UI::set_layout ($::CFG->{layout});
2209 root 1.1
2210     my %DEF_CFG = (
2211 root 1.216 sdl_mode => 0,
2212     fullscreen => 0,
2213     fast => 0,
2214 root 1.217 force_opengl11 => undef,
2215 root 1.216 texture_compression => 1,
2216     map_scale => 1,
2217     fow_enable => 1,
2218     fow_intensity => 0,
2219     map_smoothing => 1,
2220     gui_fontsize => 1,
2221     log_fontsize => 0.7,
2222     gauge_fontsize => 1,
2223     gauge_size => 0.35,
2224     stat_fontsize => 0.7,
2225     mapsize => 100,
2226     audio_enable => 1,
2227     effects_enable => 1,
2228     effects_volume => 1,
2229     bgm_enable => 1,
2230     bgm_volume => 0.5,
2231     output_sync => 1,
2232     output_count => 1,
2233     output_rate => "",
2234     pickup => 0,
2235     inv_sort => "mtime",
2236     default => "profile", # default profile
2237     show_tips => 1,
2238     logview_max_par => 1000,
2239 root 1.1 );
2240 root 1.75
2241 root 1.1 while (my ($k, $v) = each %DEF_CFG) {
2242     $CFG->{$k} = $v unless exists $CFG->{$k};
2243     }
2244    
2245 root 1.75 $CFG->{profile}{default}{host} ||= "crossfire.schmorp.de";
2246 root 1.137 $PROFILE = $CFG->{profile}{default};
2247    
2248     # convert old bindings (only default profile matters)
2249     if (my $bindings = delete $PROFILE->{bindings}) {
2250     while (my ($mod, $syms) = each %$bindings) {
2251     while (my ($sym, $cmds) = each %$syms) {
2252     push @{ $PROFILE->{macro} }, {
2253     accelkey => [$mod*1, $sym*1],
2254     action => $cmds,
2255     };
2256     }
2257     }
2258     }
2259 root 1.75
2260 root 1.1 sdl_init;
2261    
2262 root 1.172 @SDL_MODES = CFPlus::SDL_ListModes 8, 8;
2263     @SDL_MODES = CFPlus::SDL_ListModes 5, 0 unless @SDL_MODES;
2264 root 1.104 @SDL_MODES or CFPlus::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
2265 root 1.1
2266 root 1.177 @SDL_MODES = sort { $a->[0] * $a->[1] <=> $b->[0] * $b->[1] } @SDL_MODES;
2267    
2268 root 1.1 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
2269    
2270     {
2271 root 1.104 my @fonts = map CFPlus::find_rcfile "fonts/$_", qw(
2272 root 1.1 DejaVuSans.ttf
2273     DejaVuSansMono.ttf
2274     DejaVuSans-Bold.ttf
2275     DejaVuSansMono-Bold.ttf
2276     DejaVuSans-Oblique.ttf
2277     DejaVuSansMono-Oblique.ttf
2278     DejaVuSans-BoldOblique.ttf
2279     DejaVuSansMono-BoldOblique.ttf
2280     );
2281    
2282 root 1.104 CFPlus::add_font $_ for @fonts;
2283 root 1.1
2284 root 1.104 CFPlus::pango_init;
2285 root 1.1
2286 root 1.104 $FONT_PROP = new_from_file CFPlus::Font $fonts[0];
2287     $FONT_FIXED = new_from_file CFPlus::Font $fonts[1];
2288 root 1.1
2289     $FONT_PROP->make_default;
2290     }
2291    
2292     # compare mono (ft) vs. rgba (cairo)
2293     # ft - 1.8s, cairo 3s, even in alpha-only mode
2294     # for my $rgba (0..1) {
2295     # my $t1 = Time::HiRes::time;
2296     # for (1..1000) {
2297 root 1.104 # my $layout = CFPlus::Layout->new ($rgba);
2298 root 1.1 # $layout->set_text ("hallo" x 100);
2299     # $layout->render;
2300     # }
2301     # my $t2 = Time::HiRes::time;
2302     # warn $t2-$t1;
2303     # }
2304    
2305 root 1.122 $startup_done->();
2306    
2307 root 1.1 video_init;
2308     audio_init;
2309     }
2310    
2311 root 1.119 show_tip_of_the_day if $CFG->{show_tips};
2312    
2313 root 1.1 Event::loop;
2314    
2315 root 1.213 #video_shutdown;
2316     #audio_shutdown;
2317     CFPlus::SDL_Quit;
2318     CFPlus::DB::Server::stop;
2319 root 1.1
2320     =head1 NAME
2321    
2322 root 1.204 cfplus - A Crossfire TRT and Crossfire game client
2323 root 1.1
2324     =head1 SYNOPSIS
2325    
2326     Just run it - no commandline arguments are supported.
2327    
2328     =head1 USAGE
2329    
2330 root 1.204 cfplus utilises OpenGL for all UI elements and the game. It is supposed to
2331     be used in fullscreen mode and interactively.
2332 root 1.1
2333 root 1.39 =head1 DEBUGGING
2334    
2335    
2336     CFPLUS_DEBUG - environment variable
2337    
2338     1 draw borders around widgets
2339     2 add low-level widget info to tooltips
2340     4 show fps
2341     8 suppress tooltips
2342    
2343 root 1.1 =head1 AUTHOR
2344    
2345     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2346    
2347    
2348