ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.213
Committed: Mon Aug 13 16:46:08 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.212: +6 -10 lines
Log Message:
due to windows/perl bugs we cannot use END blocks to clean up as perl freezes when trying to clean-up before running the END blocks :/

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