ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.231
Committed: Sun Oct 14 10:33:50 2007 UTC (16 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.230: +39 -24 lines
Log Message:
splitted up the server setup in login and server setup

File Contents

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