ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.215
Committed: Fri Aug 17 21:18:01 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.214: +2 -4 lines
Log Message:
- optimise widget protocol a bit (incompatible change).
- implement well-known-widgets on server and client side,
  marginally test.
- add "title" parameter for infobox.

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