ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.207
Committed: Thu Aug 9 11:02:08 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.206: +1 -7 lines
Log Message:
implement set_positon_r and use it

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