ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.204
Committed: Mon Aug 6 05:06:33 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.203: +5 -5 lines
Log Message:
experimental delaying of uploasding textures to the gfx card, also store textures on disk only as the memory usage is unbearable otherwise. this most notably breaks CFPlus::UI::Face

File Contents

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