ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.208
Committed: Fri Aug 10 04:02:14 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.207: +10 -16 lines
Log Message:
- detect missing tiles or faces in map1a
- delay map drawing a tiny bit when some faces are missing, in the
  hope of being able to fetch them from the database.
- do not rate-limit refreshes to 60hz
- do refresh in an idle handler
- poll events independently from refresh

File Contents

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