ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.205
Committed: Thu Aug 9 03:40:02 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.204: +13 -5 lines
Log Message:
some simplifications, bugfixes and minor improvements

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