ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.201
Committed: Fri Aug 3 02:20:32 2007 UTC (16 years, 10 months ago) by root
Branch: MAIN
Changes since 1.200: +26 -3 lines
Log Message:
finally we have the licensing page in place (http://data.plan9.de/licensing.jpg),
enabling us to legally use creative common licensed stuff.

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