ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
(Generate patch)

Comparing deliantra/Deliantra-Client/bin/cfplus (file contents):
Revision 1.188 by root, Sun Jul 29 03:58:26 2007 UTC vs.
Revision 1.212 by root, Sun Aug 12 13:09:44 2007 UTC

71unshift @INC, $ENV{PAR_TEMP} 71unshift @INC, $ENV{PAR_TEMP}
72 if %PAR::LibCache; 72 if %PAR::LibCache;
73 73
74use Time::HiRes 'time'; 74use Time::HiRes 'time';
75use Event; 75use Event;
76use List::Util qw(max min);
76 77
77use Crossfire; 78use Crossfire;
78use Crossfire::Protocol::Constants; 79use Crossfire::Protocol::Constants;
79 80
80use Compress::LZF; 81use Compress::LZF;
112our $CONN; 113our $CONN;
113our $PROFILE; # current profile 114our $PROFILE; # current profile
114our $FAST; # fast, low-quality mode, possibly useful for software-rendering 115our $FAST; # fast, low-quality mode, possibly useful for software-rendering
115 116
116our $WANT_REFRESH; 117our $WANT_REFRESH;
117our $CAN_REFRESH;
118 118
119our @SDL_MODES; 119our @SDL_MODES;
120our $WIDTH; 120our $WIDTH;
121our $HEIGHT; 121our $HEIGHT;
122our $FULLSCREEN; 122our $FULLSCREEN;
143our $SETUP_KEYBOARD; 143our $SETUP_KEYBOARD;
144 144
145our $PL_NOTEBOOK; 145our $PL_NOTEBOOK;
146our $PL_WINDOW; 146our $PL_WINDOW;
147 147
148our $MUSIC_PLAYING_WIDGET;
149our $LICENSE_WIDGET;
150
148our $INVENTORY_PAGE; 151our $INVENTORY_PAGE;
149our $STATS_PAGE; 152our $STATS_PAGE;
150our $SKILL_PAGE; 153our $SKILL_PAGE;
151our $SPELL_PAGE; 154our $SPELL_PAGE;
152our $SPELL_LIST; 155our $SPELL_LIST;
158our $STATWIDS; 161our $STATWIDS;
159 162
160our $SDL_ACTIVE; 163our $SDL_ACTIVE;
161our %SDL_CB; 164our %SDL_CB;
162 165
163our $SDL_MIXER;
164our $MUSIC_DEFAULT = "in_a_heartbeat.ogg";
165our @MUSIC_WANT;
166our $MUSIC_START;
167our $MUSIC_PLAYING;
168our $MUSIC_PLAYER;
169our $MUSIC_RESUME = 30; # resume music when players less than these many seconds before
170our @SOUNDS; # event => file mapping
171our %AUDIO_CHUNKS; # audio files
172
173our $ALT_ENTER_MESSAGE; 166our $ALT_ENTER_MESSAGE;
174our $STATUSBOX; 167our $STATUSBOX;
175our $DEBUG_STATUS; 168our $DEBUG_STATUS;
176 169
177our $INV; 170our $INV;
178our $INVR; 171our $INVR;
179our $INV_RIGHT_HB; 172our $INV_RIGHT_HB;
180 173
181our $PICKUP_CFG; 174our $PICKUP_CFG;
182 175
176#############################################################################
177
183sub status { 178sub status {
184 $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]); 179 $STATUSBOX->add (CFPlus::asxml $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
185} 180}
186 181
187sub debug { 182sub debug {
190 185
191sub message { 186sub message {
192 my ($para) = @_; 187 my ($para) = @_;
193 $MESSAGE_WINDOW->message ($para); 188 $MESSAGE_WINDOW->message ($para);
194} 189}
190
191#############################################################################
192#TODO: maybe move into own audio module...
193
194our $SDL_MIXER;
195
196our $MUSIC_DEFAULT = "in_a_heartbeat.ogg";
197our $MUSIC_WANT; # arryref of ambient music we want to play
198our @MUSIC_HAVE; # ambient music we have on disk
199our $MUSIC_START;
200our @MUSIC_JINGLE; # which jingles to play next
201our $MUSIC_PLAYING_DATA;
202our $MUSIC_PLAYING_META;
203our $MUSIC_PLAYER;
204our $MUSIC_RESUME = 30; # resume music when played less than these many seconds before
205
206our %AUDIO_CHUNK; # audio "files"
207our %AUDIO_PLAY; # which audio faces should be played
208
209sub audio_channel_finished {
210 my ($channel) = @_;
211
212# warn "channel $channel finished\n";#d#
213}
214
215sub audio_sound_push($) {
216 my ($face) = @_;
217
218 $CFG->{effects_enable}
219 or return;
220
221 $AUDIO_PLAY{$face}
222 or return;
223
224 if (my $chunk = $AUDIO_CHUNK{$face}) {
225 for (grep $_->[0] >= Event::time, @{(delete $AUDIO_PLAY{$face}) || []}) {
226 my (undef, $dx, $dy, $vol) = @$_;
227
228 my $channel = CFPlus::Channel::find;
229 $channel->volume ($vol * $CFG->{effects_volume} * 128 / 255);
230 $channel->set_position_r ($dx, $dy, 20);
231 $chunk->play ($channel);
232 }
233 } else {
234 # sound_meta not set means data is in flight either way
235 my $meta = $CONN->{sound_meta}{$face}
236 or return;
237
238 # if its a jingle, play it as ambient music
239 if ($meta->{meta}{jingle}) {
240 if (delete $AUDIO_PLAY{$face}) { # take the jingle out of the sound queue
241 push @MUSIC_JINGLE, $meta; # push it oto the music/jingle queue
242 &audio_music_push ($face);
243 }
244 } else {
245 # fetch from database
246 CFPlus::DB::get res_data => $meta->{name}, sub {
247 my $rwops = new CFPlus::RW $_[0];
248 my $chunk = new CFPlus::MixChunk $rwops
249 or Carp::confess "sound face " . (JSON::XS::to_json $meta) . " unloadable: " . CFPlus::Mix_GetError;
250 $chunk->volume (($meta->{meta}{volume} || 1) * 128);
251 $AUDIO_CHUNK{$face} = $chunk;
252
253 audio_sound_push ($face);
254 };
255 }
256 }
257}
258
259sub audio_sound_play {
260 my ($face, $dx, $dy, $vol) = @_;
261
262 $SDL_MIXER
263 or return;
264 $CFG->{effects_enable}
265 or return;
266
267 my $queue = $AUDIO_PLAY{$face} ||= [];
268 push @$queue, [Event::time + 0.6, $dx, $dy, $vol]; # do not play sound for outdated events
269 audio_sound_push $face
270 unless @$queue > 1;
271}
272
273sub audio_music_set_meta {
274 my ($meta) = @_;
275
276 $MUSIC_PLAYING_META = $meta;
277 $MUSIC_PLAYING_WIDGET->set_markup (
278 "<b>Name</b>: " . (CFPlus::asxml $meta->{meta}{name}) . "\n"
279 . "<b>Author</b>: " . (CFPlus::asxml $meta->{meta}{author}) . "\n"
280 . "<b>Source</b>: " . (CFPlus::asxml $meta->{meta}{source}) . "\n"
281 . "<b>License</b>: " . (CFPlus::asxml $meta->{meta}{license})
282 );
283}
284
285sub audio_music_update_volume {
286 return unless $MUSIC_PLAYING_META;
287 my $volume = $MUSIC_PLAYING_META->{meta}{volume} || 1;
288 my $base = $MUSIC_PLAYING_META->{meta}{jingle} ? 1 : $CFG->{bgm_volume};
289 CFPlus::MixMusic::volume $base * $volume * 128;
290}
291
292sub audio_music_start {
293 my $meta = $MUSIC_PLAYING_META;
294
295 CFPlus::DB::get res_data => $meta->{name}, sub {
296 return unless $SDL_MIXER;
297
298 # music might have changed...
299 $meta eq $MUSIC_PLAYING_META
300 or return &audio_music_start ();
301
302 audio_music_update_volume;
303
304 $MUSIC_PLAYING_DATA = \$_[0];
305
306 my $rwops = $meta->{path}
307 ? new_from_file CFPlus::RW $meta->{path}
308 : new CFPlus::RW $$MUSIC_PLAYING_DATA;
309
310 $MUSIC_PLAYER = new CFPlus::MixMusic $rwops
311 or Carp::confess "music face $meta->{face} unloadable: " . CFPlus::Mix_GetError;
312
313 my $NOW = time;
314
315 if ($MUSIC_PLAYING_META->{stop_time} > $NOW - $MUSIC_RESUME) {
316 my $pos = $MUSIC_PLAYING_META->{stop_pos};
317 $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
318 $MUSIC_START = time - $pos;
319 } else {
320 $MUSIC_PLAYER->play (0);
321 $MUSIC_START = time;
322 }
323
324 delete $meta->{stop_time};
325 delete $meta->{stop_pos};
326 }
327}
328
329sub audio_music_push {
330 return unless $SDL_MIXER;
331
332 my $fade_out;
333
334 if (@MUSIC_JINGLE) {
335 @MUSIC_HAVE = $MUSIC_JINGLE[0];
336 $fade_out = 333;
337 } else {
338 return unless $CFG->{bgm_enable};
339
340 my @have =
341 grep $_,
342 map $CONN->{music_meta}{$_},
343 @$MUSIC_WANT;
344
345 @MUSIC_HAVE = @have
346 if @have;
347
348 # default MUSIC_HAVE == MUSIC_DEFAULT
349 @MUSIC_HAVE = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_HAVE;
350 $fade_out = 1000;
351 }
352
353 # if the currently playing song is acceptable, let it continue
354 return if grep $MUSIC_PLAYING_META == $_, @MUSIC_HAVE;
355
356 my $NOW = time;
357
358 if ($MUSIC_PLAYING_META) {
359 $MUSIC_PLAYING_META->{stop_time} = $NOW;
360 $MUSIC_PLAYING_META->{stop_pos} = $NOW - $MUSIC_START;
361 CFPlus::MixMusic::fade_out $fade_out;
362 } else {
363 # sort by stop time, oldest first
364 @MUSIC_HAVE = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_HAVE;
365
366 # if the most recently-played piece played very recently,
367 # resume it, else choose the oldest piece for rotation.
368 audio_music_set_meta
369 $MUSIC_HAVE[-1]{stop_time} > $NOW - $MUSIC_RESUME
370 ? $MUSIC_HAVE[-1]
371 : $MUSIC_HAVE[0];
372
373 audio_music_start;
374 }
375}
376
377sub audio_music_set_ambient {
378 my ($songs) = @_;
379
380 $MUSIC_WANT = $songs;
381 audio_music_push;
382}
383
384sub audio_music_finished {
385 # we compress multiple jingles of the same type
386 shift @MUSIC_JINGLE
387 while @MUSIC_JINGLE && $MUSIC_PLAYING_META == $MUSIC_JINGLE[0];
388
389 $MUSIC_PLAYING_WIDGET->clear;
390
391 undef $MUSIC_PLAYER;
392 undef $MUSIC_PLAYING_META;
393 undef $MUSIC_PLAYING_DATA;
394
395 audio_music_push;
396}
397
398sub audio_init {
399 if ($CFG->{audio_enable}) {
400 $ENV{MIX_EFFECTSMAXSPEED} = 1;
401 $SDL_MIXER = !CFPlus::Mix_OpenAudio;
402
403 unless ($SDL_MIXER) {
404 status "Unable to open sound device: there will be no sound";
405 return;
406 }
407
408 CFPlus::Mix_AllocateChannels 16;
409
410 audio_music_finished;
411 } else {
412 undef $SDL_MIXER;
413 }
414}
415
416sub audio_shutdown {
417 undef $MUSIC_PLAYER;
418 undef $MUSIC_PLAYING_META;
419 undef $MUSIC_PLAYING_DATA;
420
421 $MUSIC_WANT = [];
422 @MUSIC_JINGLE = ();
423 %AUDIO_PLAY = ();
424 %AUDIO_CHUNK = ();
425
426 CFPlus::Mix_CloseAudio if $SDL_MIXER;
427 undef $SDL_MIXER;
428}
429
430#############################################################################
195 431
196sub destroy_query_dialog { 432sub destroy_query_dialog {
197 (delete $_[0]{query_dialog})->destroy 433 (delete $_[0]{query_dialog})->destroy
198 if $_[0]{query_dialog}; 434 if $_[0]{query_dialog};
199} 435}
459 query => \&server_query, 695 query => \&server_query,
460 696
461 setup_req => { 697 setup_req => {
462 smoothing => $CFG->{map_smoothing}*1, 698 smoothing => $CFG->{map_smoothing}*1,
463 }, 699 },
464
465 sound_play => sub {
466 my ($x, $y, $soundnum, $type) = @_;
467
468 $SDL_MIXER
469 or return;
470
471 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
472 or return;
473
474 $chunk->play;
475 },
476 }; 700 };
477 701
478 if ($CONN) { 702 if ($CONN) {
479 CFPlus::lowdelay fileno $CONN->{fh}; 703 CFPlus::lowdelay fileno $CONN->{fh};
480 704
491 $SETUP_DIALOG->show; 715 $SETUP_DIALOG->show;
492 $PL_WINDOW->hide; 716 $PL_WINDOW->hide;
493 $SPELL_LIST->clear_spells; 717 $SPELL_LIST->clear_spells;
494 $CFPlus::UI::ROOT->emit (stop_game => ! ! $CONN); 718 $CFPlus::UI::ROOT->emit (stop_game => ! ! $CONN);
495 719
496 &audio_music_set ([]); 720 &audio_music_set_ambient ([]);
497 721
498 return unless $CONN; 722 return unless $CONN;
499 723
500 status "connection closed"; 724 status "connection closed";
501 725
543 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fullscreen"); 767 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fullscreen");
544 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new CFPlus::UI::CheckBox 768 $table->add_at (1, $row++, $FULLSCREEN_ENABLE = new CFPlus::UI::CheckBox
545 state => $CFG->{fullscreen}, 769 state => $CFG->{fullscreen},
546 tooltip => "Bring the client into fullscreen mode.", 770 tooltip => "Bring the client into fullscreen mode.",
547 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 } 771 on_changed => sub { my ($self, $value) = @_; $CFG->{fullscreen} = $value; 0 }
772 );
773
774 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Force OpenGL 1.1");
775 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
776 state => $CFG->{opengl11},
777 tooltip => "Limit CFPlus to use OpenGL 1.1 features only. This will normally result in "
778 . "higher memory usage and slower performance. It will, however, help tremendously on "
779 . "cards that claim to support a feature but fall back to software rendering. "
780 . "Nvidia Geforce FX cards are known to claim features the hardware doesn't support, "
781 . "but cards and drivers from other vendors (ATI) are often just as bad. <b>If you "
782 . "experience extremely low framerates and your card should do better, try this option.</b>",
783 on_changed => sub { my ($self, $value) = @_; $CFG->{opengl11} = $value; 0 }
548 ); 784 );
549 785
550 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fast & Ugly"); 786 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
551 $table->add_at (1, $row++, new CFPlus::UI::CheckBox 787 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
552 state => $CFG->{fast}, 788 state => $CFG->{fast},
580 816
581 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Smoothing"); 817 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Smoothing");
582 $table->add_at (1, $row++, new CFPlus::UI::CheckBox 818 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
583 state => $CFG->{map_smoothing}, 819 state => $CFG->{map_smoothing},
584 tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. " 820 tooltip => "<b>Map Smoothing</b> tries to make tile borders less square. "
585 . "This increases load on the graphics subsystem and works only with 2.x servers. " 821 . "This increases load on the graphics subsystem and works only with TRT servers. "
586 . "Changes take effect at next connection only.", 822 . "Changes take effect at next login only.",
587 on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 } 823 on_changed => sub { my ($self, $value) = @_; $CFG->{map_smoothing} = $value; 0 }
588 ); 824 );
589 825
590 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fog of War"); 826 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Fog of War");
591 $table->add_at (1, $row++, new CFPlus::UI::CheckBox 827 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
602 ); 838 );
603 839
604 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Message Fontsize"); 840 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Message Fontsize");
605 $table->add_at (1, $row++, new CFPlus::UI::Slider 841 $table->add_at (1, $row++, new CFPlus::UI::Slider
606 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1], 842 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
607 tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.", 843 tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant, "
844 . "but you still need to press apply to correctly re-layout the widget.",
608 on_changed => sub { $MESSAGE_WINDOW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 }, 845 on_changed => sub { $MESSAGE_WINDOW->set_fontsize ($CFG->{log_fontsize} = $_[1]); 0 },
609 ); 846 );
610 847
611 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge fontsize"); 848 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
612 $table->add_at (1, $row++, new CFPlus::UI::Slider 849 $table->add_at (1, $row++, new CFPlus::UI::Slider
634} 871}
635 872
636sub audio_setup { 873sub audio_setup {
637 my $vbox = new CFPlus::UI::VBox; 874 my $vbox = new CFPlus::UI::VBox;
638 875
639 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 1]); 876 $vbox->add (my $table = new CFPlus::UI::Table expand => 1, col_expand => [0, 0, 1]);
640 877
641 my $row = 0; 878 my $row = 0;
642 879
643 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable"); 880 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Audio Enable");
644 $table->add_at (1, $row++, new CFPlus::UI::CheckBox 881 $table->add_at (1, $row++, new CFPlus::UI::CheckBox
648 ); 885 );
649# $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Effects Volume"); 886# $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Effects Volume");
650# $table->add_at (1, 8, new CFPlus::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub { 887# $table->add_at (1, 8, new CFPlus::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
651# $CFG->{effects_volume} = $_[1]; 888# $CFG->{effects_volume} = $_[1];
652# }); 889# });
890
891 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Sound Effects");
892 $table->add_at (1, $row, new CFPlus::UI::CheckBox
893 expand => 1, state => $CFG->{effects_enable},
894 tooltip => "If enabled, sound effects are enabled. If disabled, no sound effects will be played.",
895 on_changed => sub {
896 $CFG->{effects_enable} = $_[1];
897 $CONN->update_fx_want if $CONN;
898 0
899 }
900 );
901 $table->add_at (2, $row++, new CFPlus::UI::Slider
902 expand => 1, range => [$CFG->{effects_volume}, 0, 1, 0, 1/128],
903 tooltip => "The relative volume of sound effects. Best audio quality is achieved if this "
904 . "is set highest (rightmost) and you use your operating system volume setting. Changes are instant.",
905 on_changed => sub { $CFG->{effects_volume} = $_[1]; 0 }
906 );
907
653 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music"); 908 $table->add_at (0, $row, new CFPlus::UI::Label valign => 0, align => 1, text => "Background Music");
654 $table->add_at (1, $row++, my $hbox = new CFPlus::UI::HBox); 909 $table->add_at (1, $row, new CFPlus::UI::CheckBox
655 $hbox->add (new CFPlus::UI::CheckBox
656 expand => 1, state => $CFG->{bgm_enable}, 910 expand => 1, state => $CFG->{bgm_enable},
657 tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.", 911 tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
658 on_changed => sub { $CFG->{bgm_enable} = $_[1]; 0 } 912 on_changed => sub {
913 $CFG->{bgm_enable} = $_[1];
914 $CONN->update_fx_want if $CONN;
915 0
916 }
659 ); 917 );
660 $hbox->add (new CFPlus::UI::Slider 918 $table->add_at (2, $row++, new CFPlus::UI::Slider
661 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128], 919 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
662 tooltip => "The volume of the background music. Changes are instant.", 920 tooltip => "The volume of the background music. Changes are instant.",
663 on_changed => sub { $CFG->{bgm_volume} = $_[1]; CFPlus::MixMusic::volume $_[1] * 128; 0 } 921 on_changed => sub { $CFG->{bgm_volume} = $_[1]; audio_music_update_volume; 0 }
664 ); 922 );
665 923
666 $table->add_at (1, $row++, new CFPlus::UI::Button 924 $table->add_at (1, $row++, new CFPlus::UI::Button
667 expand => 1, align => 0, text => "Apply", 925 c_colspan => 2, expand => 1, align => 0, text => "Apply",
668 tooltip => "Apply the audio settings", 926 tooltip => "Apply the audio settings",
669 on_activate => sub { 927 on_activate => sub {
670 audio_shutdown (); 928 audio_shutdown ();
671 audio_init (); 929 audio_init ();
672 0 930 0
743 $table->add_at (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } ); 1001 $table->add_at (0, 4, new CFPlus::UI::Button text => "die on click(tm)", on_activate => sub { &CFPlus::debug() } );
744 1002
745 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d# 1003 $table->add_at (0, 5, new CFPlus::UI::TextEdit text => "line1\0152\0153");#d#
746 1004
747 $table->add_at (7,7, my $t = new CFPlus::UI::Table expand => 0); 1005 $table->add_at (7,7, my $t = new CFPlus::UI::Table expand => 0);
748 $t->add_at (0,0, new CFPlus::UI::Label text => "a a a a", rowspan => 1, colspan => 2); 1006 $t->add_at (0,0, new CFPlus::UI::Label text => "a a a a", c_rowspan => 1, c_colspan => 2);
749 $t->add_at (2,0, new CFPlus::UI::Label text => "b\nb", rowspan => 2, colspan => 1); 1007 $t->add_at (2,0, new CFPlus::UI::Label text => "b\nb", c_rowspan => 2, c_colspan => 1);
750 $t->add_at (1,2, new CFPlus::UI::Label text => "c c c c", rowspan => 1, colspan => 2); 1008 $t->add_at (1,2, new CFPlus::UI::Label text => "c c c c", c_rowspan => 1, c_colspan => 2);
751 $t->add_at (0,1, new CFPlus::UI::Label text => "d\nd", rowspan => 2, colspan => 1); 1009 $t->add_at (0,1, new CFPlus::UI::Label text => "d\nd", c_rowspan => 2, c_colspan => 1);
752 $t->add_at (1,1, new CFPlus::UI::Label text => "e"); 1010 $t->add_at (1,1, new CFPlus::UI::Label text => "e");
753 1011
754 $table->add_at (7, 6, my $c = new CFPlus::UI::Canvas); 1012 $table->add_at (7, 6, my $c = new CFPlus::UI::Canvas);
755 1013
756 $c->add_items ({ 1014 $c->add_items ({
1081 text => $CFG->{profile}{default}{host}, 1339 text => $CFG->{profile}{default}{host},
1082 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to", 1340 tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
1083 on_changed => sub { 1341 on_changed => sub {
1084 my ($self, $value) = @_; 1342 my ($self, $value) = @_;
1085 $CFG->{profile}{default}{host} = $value; 1343 $CFG->{profile}{default}{host} = $value;
1086 0 1344 1
1087 } 1345 }
1088 ); 1346 );
1089 1347
1090 $vbox->add (new CFPlus::UI::Button 1348 $vbox->add (new CFPlus::UI::Button
1091 expand => 1, 1349 expand => 1,
1092 text => "Server List", 1350 text => "Server List",
1093 other => $METASERVER, 1351 other => $METASERVER,
1094 tooltip => "Show a list of available crossfire servers", 1352 tooltip => "Show a list of available crossfire servers",
1095 on_activate => sub { $METASERVER->toggle_visibility; 0 }, 1353 on_activate => sub { $METASERVER->toggle_visibility; 0 },
1096 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 0 }, 1354 on_visibility_change => sub { $METASERVER->hide unless $_[1]; 1 },
1097 ); 1355 );
1098 } 1356 }
1099 1357
1100 $table->add_at (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username"); 1358 $table->add_at (0, 4, new CFPlus::UI::Label valign => 0, align => 1, text => "Username");
1101 $table->add_at (1, 4, new CFPlus::UI::Entry 1359 $table->add_at (1, 4, new CFPlus::UI::Entry
1102 text => $CFG->{profile}{default}{user}, 1360 text => $CFG->{profile}{default}{user},
1103 tooltip => "The name of your character on the server", 1361 tooltip => "The name of your character on the server",
1104 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value } 1362 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{user} = $value; 1 }
1105 ); 1363 );
1106 1364
1107 $table->add_at (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password"); 1365 $table->add_at (0, 5, new CFPlus::UI::Label valign => 0, align => 1, text => "Password");
1108 $table->add_at (1, 5, new CFPlus::UI::Entry 1366 $table->add_at (1, 5, new CFPlus::UI::Entry
1109 text => $CFG->{profile}{default}{password}, 1367 text => $CFG->{profile}{default}{password},
1110 hidden => 1, 1368 hidden => 1,
1111 tooltip => "The password for your character", 1369 tooltip => "The password for your character",
1112 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value } 1370 on_changed => sub { my ($self, $value) = @_; $CFG->{profile}{default}{password} = $value; 1 }
1113 ); 1371 );
1114 1372
1115 $table->add_at (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size"); 1373 $table->add_at (0, 7, new CFPlus::UI::Label valign => 0, align => 1, text => "Map Size");
1116 $table->add_at (1, 7, new CFPlus::UI::Slider 1374 $table->add_at (1, 7, new CFPlus::UI::Slider
1117 force_w => 100, 1375 force_w => 100,
1118 range => [$CFG->{mapsize}, 10, 100, 0, 1], 1376 range => [$CFG->{mapsize}, 10, 100, 0, 1],
1119 tooltip => "This is the size of the portion of the map update the server sends you. " 1377 tooltip => "This is the size of the portion of the map update the server sends you. "
1120 . "If you set this to a high value you will be able to see further, " 1378 . "If you set this to a high value you will be able to see further, "
1121 . "but you also increase bandwidth requirements and latency. " 1379 . "but you also increase bandwidth requirements and latency. "
1122 . "This option is only used once at log-in.", 1380 . "This option is only used once at log-in.",
1123 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 0 }, 1381 on_changed => sub { my ($self, $value) = @_; $CFG->{mapsize} = $self->{range}[0] = $value = int $value; 1 },
1124 ); 1382 );
1125 1383
1126 $table->add_at (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Face Prefetch");
1127 $table->add_at (1, 8, new CFPlus::UI::CheckBox
1128 state => $CFG->{face_prefetch},
1129 tooltip => "<b>Background Image Prefetch</b>\n\n"
1130 . "If enabled, the client automatically pre-fetches images from the server. "
1131 . "This might increase or create lag, but increases the chances "
1132 . "of faces being ready for display when you encounter them. "
1133 . "It also uses up server bandwidth on every connect, "
1134 . "so only set it if you really need to prefetch images. "
1135 . "This option can be set and unset any time.",
1136 on_changed => sub { $CFG->{face_prefetch} = $_[1]; 0 },
1137 );
1138
1139 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate"); 1384 $table->add_at (0, 8, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Rate");
1385 $table->add_at (1, 8, new CFPlus::UI::Entry
1386 text => $CFG->{output_rate},
1387 tooltip => "The maximum bandwidth in bytes per second that the server should not exceed "
1388 . "when sending data. When 0 or unset, the server "
1389 . "default will be used, which is usually around 100kb/s. Most servers will "
1390 . "dynamically find an optimal rate, so adjust this only when necessary.",
1391 on_changed => sub { $CFG->{output_rate} = $_[1]; 1 },
1392 );
1393
1394 $table->add_at (0, 9, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1140 $table->add_at (1, 9, new CFPlus::UI::Entry 1395 $table->add_at (1, 9, new CFPlus::UI::Entry
1141 text => $CFG->{output_rate},
1142 tooltip => "The approximate bandwidth in bytes per second that the server should not exceed "
1143 . "when sending images, to ensure interactiveness. When 0 or unset, the server "
1144 . "default will be used, which is usually around 100kb/s.",
1145 on_changed => sub { $CFG->{output_rate} = $_[1]; 0 },
1146 );
1147
1148 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Count");
1149 $table->add_at (1, 10, new CFPlus::UI::Entry
1150 text => $CFG->{output_count}, 1396 text => $CFG->{output_count},
1151 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.", 1397 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1152 on_changed => sub { $CFG->{output_count} = $_[1]; 0 }, 1398 on_changed => sub { $CFG->{output_count} = $_[1]; 1 },
1153 ); 1399 );
1154 1400
1155 $table->add_at (0, 11, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync"); 1401 $table->add_at (0, 10, new CFPlus::UI::Label valign => 0, align => 1, text => "Output-Sync");
1156 $table->add_at (1, 11, new CFPlus::UI::Entry 1402 $table->add_at (1, 10, new CFPlus::UI::Entry
1157 text => $CFG->{output_sync}, 1403 text => $CFG->{output_sync},
1158 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.", 1404 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
1159 on_changed => sub { $CFG->{output_sync} = $_[1]; 0 }, 1405 on_changed => sub { $CFG->{output_sync} = $_[1]; 1 },
1160 ); 1406 );
1161 1407
1162 $table->add_at (1, 12, $LOGIN_BUTTON = new CFPlus::UI::Button 1408 $table->add_at (1, 11, $LOGIN_BUTTON = new CFPlus::UI::Button
1163 expand => 1, 1409 expand => 1,
1164 align => 0, 1410 align => 0,
1165 text => "Login", 1411 text => "Login",
1166 on_activate => sub { 1412 on_activate => sub {
1167 $CONN ? stop_game 1413 $CONN ? stop_game
1168 : start_game; 1414 : start_game;
1169 0 1415 1
1170 }, 1416 },
1171 ); 1417 );
1172 1418
1173 $vbox->add (new CFPlus::UI::FancyFrame 1419 $vbox->add (new CFPlus::UI::FancyFrame
1174 label => "Server Info", 1420 label => "Server Info",
1375 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor"); 1621 CFPlus::Protocol::set_opencont ($::CONN, 0, "Floor");
1376 1622
1377 $hb 1623 $hb
1378} 1624}
1379 1625
1626sub media_window {
1627 my $vb = new CFPlus::UI::VBox;
1628
1629 $vb->add (new CFPlus::UI::FancyFrame
1630 label => "Currently playing music",
1631 child => new CFPlus::UI::ScrolledWindow scroll_x => 1, scroll_y => 0,
1632 child => ($MUSIC_PLAYING_WIDGET = new CFPlus::UI::Label ellipsise => 0, fontsize => 0.8),
1633 );
1634
1635 $vb->add (new CFPlus::UI::FancyFrame
1636 label => "Other media used in this session",
1637 expand => 1,
1638 child => ($LICENSE_WIDGET = new CFPlus::UI::TextScroller
1639 expand => 1, fontsize => 0.8, padding_x => 4, padding_y => 4),
1640 );
1641
1642 $vb
1643}
1644
1645sub add_license {
1646 my ($meta) = @_;
1647
1648 $meta = $meta->{meta}
1649 or return;
1650
1651 $meta->{license} || $meta->{author} || $meta->{source}
1652 or return;
1653
1654 $LICENSE_WIDGET->add_paragraph ({
1655 fg => [1, 1, 1, 1],
1656 markup => "<small>"
1657 . "<b>Name:</b> " . (CFPlus::asxml $meta->{name}) . "\n"
1658 . "<b>Author:</b> " . (CFPlus::asxml $meta->{author}) . "\n"
1659 . "<b>Source:</b> " . (CFPlus::asxml $meta->{source}) . "\n"
1660 . "<b>License:</b> " . (CFPlus::asxml $meta->{license}) . "\n"
1661 . "</small>",
1662 });
1663 $LICENSE_WIDGET->scroll_to_bottom;
1664}
1665
1380sub toggle_player_page { 1666sub toggle_player_page {
1381 my ($widget) = @_; 1667 my ($widget) = @_;
1382 1668
1383 if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) { 1669 if ($PL_WINDOW->{visible} && $PL_NOTEBOOK->get_current_page == $widget) {
1384 $PL_WINDOW->hide; 1670 $PL_WINDOW->hide;
1421 $ntb->add_tab ( 1707 $ntb->add_tab (
1422 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget, 1708 "Inventory (F5)" => $INVENTORY_PAGE = inventory_widget,
1423 "Toggles the inventory window, where you can manage your loot (or treasures :). " 1709 "Toggles the inventory window, where you can manage your loot (or treasures :). "
1424 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory." 1710 . "You can also hit the <b>Tab</b>-key to show/hide the Inventory."
1425 ); 1711 );
1426 $ntb->add_tab (Pickup => autopickup_setup, 1712 $ntb->add_tab (Pickup => autopickup_setup,
1427 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them."); 1713 "Configure autopickup settings, i.e. which items you will pick up automatically when walking (or running) over them.");
1714
1715 $ntb->add_tab (Media => media_window,
1716 "License, Author and Source info for media sent by the server.");
1428 1717
1429 $ntb->set_current_page ($INVENTORY_PAGE); 1718 $ntb->set_current_page ($INVENTORY_PAGE);
1430 1719
1431 $plwin->add ($ntb); 1720 $plwin->add ($ntb);
1432 $plwin 1721 $plwin
1809 CFPlus::OpenGL::shutdown; 2098 CFPlus::OpenGL::shutdown;
1810 2099
1811 undef $SDL_ACTIVE; 2100 undef $SDL_ACTIVE;
1812} 2101}
1813 2102
1814sub audio_channel_finished {
1815 my ($channel) = @_;
1816
1817 #warn "channel $channel finished\n";#d#
1818}
1819
1820sub audio_music_set {
1821 my ($songs) = @_;
1822
1823 my @want =
1824 grep $_,
1825 map $CONN->{music_meta}{$_},
1826 @$songs;
1827
1828 if (@want) {
1829 @MUSIC_WANT = @want;
1830 &audio_music_changed ();
1831 }
1832}
1833
1834sub audio_music_start {
1835 my $path = $MUSIC_PLAYING->{path}
1836 or return;
1837
1838 CFPlus::DB::prefetch_file $path, 1024_000, sub {
1839 return unless $SDL_MIXER;
1840
1841 # music might have changed...
1842 $path eq $MUSIC_PLAYING->{path}
1843 or return &audio_music_start ();
1844
1845 my $rwops = new_from_file CFPlus::RW $path;
1846 $MUSIC_PLAYER = new CFPlus::MixMusic $rwops;
1847
1848 my $NOW = time;
1849
1850 if ($MUSIC_PLAYING->{stop_time} > $NOW - $MUSIC_RESUME) {
1851 my $pos = $MUSIC_PLAYING->{stop_pos};
1852 $MUSIC_PLAYER->fade_in_pos (0, 1000, $pos);
1853 $MUSIC_START = time - $pos;
1854 } else {
1855 $MUSIC_PLAYER->play (0);
1856 $MUSIC_START = time;
1857 }
1858
1859 delete $MUSIC_PLAYING->{stop_time};
1860 delete $MUSIC_PLAYING->{stop_pos};
1861 }
1862}
1863
1864sub audio_music_changed {
1865 return unless $CFG->{bgm_enable};
1866 return unless $SDL_MIXER;
1867
1868 # default MUSIC_WANT == MUSIC_DEFAULT
1869 @MUSIC_WANT = { path => CFPlus::find_rcfile "music/$MUSIC_DEFAULT" } unless @MUSIC_WANT;
1870
1871 # if the currently playing song is acceptable, let it continue
1872 return if $MUSIC_PLAYING
1873 && grep $MUSIC_PLAYING->{path} eq $_->{path}, @MUSIC_WANT;
1874
1875 my $NOW = time;
1876
1877 if ($MUSIC_PLAYING) {
1878 $MUSIC_PLAYING->{stop_time} = $NOW;
1879 $MUSIC_PLAYING->{stop_pos} = $NOW - $MUSIC_START;
1880 CFPlus::MixMusic::fade_out 1000;
1881 } else {
1882 # sort by stop time, oldest first
1883 @MUSIC_WANT = sort { $a->{stop_time} <=> $b->{stop_time} } @MUSIC_WANT;
1884
1885 # if the most recently-played piece played very recently,
1886 # resume it, else choose the oldest piece for rotation.
1887 $MUSIC_PLAYING =
1888 $MUSIC_WANT[-1]{stop_time} > $NOW - $MUSIC_RESUME
1889 ? $MUSIC_WANT[-1]
1890 : $MUSIC_WANT[0];
1891
1892 audio_music_start;
1893 }
1894}
1895
1896sub audio_music_finished {
1897 $MUSIC_PLAYING = undef;
1898 undef $MUSIC_PLAYER;
1899
1900 audio_music_changed;
1901}
1902
1903sub audio_init {
1904 if ($CFG->{audio_enable}) {
1905 if (open my $fh, "<", CFPlus::find_rcfile "sounds/config") {
1906 $ENV{MIX_EFFECTSMAXSPEED} = 1;
1907 $SDL_MIXER = !CFPlus::Mix_OpenAudio;
1908
1909 unless ($SDL_MIXER) {
1910 status "Unable to open sound device: there will be no sound";
1911 return;
1912 }
1913
1914 CFPlus::Mix_AllocateChannels 8;
1915 CFPlus::MixMusic::volume $CFG->{bgm_volume} * 128;
1916
1917 audio_music_finished;
1918
1919 local $_;
1920 while (<$fh>) {
1921 next if /^\s*#/;
1922 next if /^\s*$/;
1923
1924 my ($file, $volume, $event) = split /\s+/, $_, 3;
1925
1926 push @SOUNDS, "$volume,$file";
1927
1928 $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1929 my $rwops = new_from_file CFPlus::RW CFPlus::find_rcfile "sounds/$file";
1930 my $chunk = new CFPlus::MixChunk $rwops;
1931 $chunk->volume ($volume * 128 / 100);
1932 $chunk
1933 };
1934 }
1935 } else {
1936 status "unable to open sound config: $!";
1937 }
1938 } else {
1939 undef $SDL_MIXER;
1940 }
1941}
1942
1943sub audio_shutdown {
1944 CFPlus::Mix_CloseAudio if $SDL_MIXER;
1945 undef $SDL_MIXER;
1946 @SOUNDS = ();
1947 %AUDIO_CHUNKS = ();
1948}
1949
1950my %animate_object; 2103my %animate_object;
1951my $animate_timer; 2104my $animate_timer;
1952 2105
1953my $fps = 9; 2106my $fps = 9;
1954 2107
1955my %demo;#d#
1956
1957sub force_refresh { 2108sub force_refresh {
2109 if ($ENV{CFPLUS_DEBUG} & 4) {
1958 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05; 2110 $fps = $fps * 0.98 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.02;
1959 debug sprintf "%3.2f", $fps if $ENV{CFPLUS_DEBUG} & 4; 2111 debug sprintf "%3.2f", $fps;
2112 }
1960 2113
1961 $CFPlus::UI::ROOT->draw; 2114 $CFPlus::UI::ROOT->draw;
1962 2115 CFPlus::SDL_GL_SwapBuffers;
1963 $WANT_REFRESH = 0;
1964 $CAN_REFRESH = 0;
1965 $LAST_REFRESH = $NOW; 2116 $LAST_REFRESH = $NOW;
1966 2117 $WANT_REFRESH->stop;
1967 CFPlus::SDL_GL_SwapBuffers;
1968} 2118}
1969 2119
2120$WANT_REFRESH = Event->idle (min => 0.001, max => 0.06, parked => 1, cb => \&force_refresh);
2121
1970my $refresh_watcher = Event->timer (after => 0, hard => 0, interval => 1 / $MAX_FPS, cb => sub { 2122my $input = Event->timer (after => 0, hard => 0, interval => 1 / 50, cb => sub {
1971 $NOW = time; 2123 $NOW = time;
1972 2124
1973 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_) 2125 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1974 for CFPlus::poll_events; 2126 for CFPlus::poll_events;
1975 2127
1976 if (%animate_object) { 2128 if (%animate_object) {
1977 $_->animate ($LAST_REFRESH - $NOW) for values %animate_object; 2129 $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1978 ++$WANT_REFRESH; 2130 $WANT_REFRESH->start;
1979 }
1980
1981 if ($WANT_REFRESH) {
1982 force_refresh;
1983 } else {
1984 $CAN_REFRESH = 1;
1985 } 2131 }
1986}); 2132});
1987 2133
1988sub animation_start { 2134sub animation_start {
1989 my ($widget) = @_; 2135 my ($widget) = @_;
1992 2138
1993sub animation_stop { 2139sub animation_stop {
1994 my ($widget) = @_; 2140 my ($widget) = @_;
1995 delete $animate_object{$widget}; 2141 delete $animate_object{$widget};
1996} 2142}
1997
1998# check once/second for faces that need to be prefetched
1999# this should, of course, only run on demand, but
2000# SDL forces worse things on us....
2001
2002Event->timer (after => 1, interval => 0.25, cb => sub {
2003 $CONN->face_prefetch
2004 if $CONN;
2005});
2006 2143
2007%SDL_CB = ( 2144%SDL_CB = (
2008 CFPlus::SDL_QUIT => sub { 2145 CFPlus::SDL_QUIT => sub {
2009 exit; 2146 exit;
2010 }, 2147 },
2054 2191
2055 CFPlus::UI::set_layout ($::CFG->{layout}); 2192 CFPlus::UI::set_layout ($::CFG->{layout});
2056 2193
2057 my %DEF_CFG = ( 2194 my %DEF_CFG = (
2058 sdl_mode => 0, 2195 sdl_mode => 0,
2059 width => 640,
2060 height => 480,
2061 fullscreen => 0, 2196 fullscreen => 0,
2062 fast => 0, 2197 fast => 0,
2198 opengl11 => 0,
2063 map_scale => 1, 2199 map_scale => 1,
2064 fow_enable => 1, 2200 fow_enable => 1,
2065 fow_intensity => 0, 2201 fow_intensity => 0,
2066 map_smoothing => 1, 2202 map_smoothing => 1,
2067 gui_fontsize => 1, 2203 gui_fontsize => 1,
2069 gauge_fontsize => 1, 2205 gauge_fontsize => 1,
2070 gauge_size => 0.35, 2206 gauge_size => 0.35,
2071 stat_fontsize => 0.7, 2207 stat_fontsize => 0.7,
2072 mapsize => 100, 2208 mapsize => 100,
2073 audio_enable => 1, 2209 audio_enable => 1,
2210 effects_enable => 1,
2211 effects_volume => 1,
2074 bgm_enable => 1, 2212 bgm_enable => 1,
2075 bgm_volume => 0.25, 2213 bgm_volume => 0.5,
2076 face_prefetch => 0,
2077 output_sync => 1, 2214 output_sync => 1,
2078 output_count => 1, 2215 output_count => 1,
2079 output_rate => "", 2216 output_rate => "",
2080 pickup => 0, 2217 pickup => 0,
2081 inv_sort => "mtime", 2218 inv_sort => "mtime",
2159Event::loop; 2296Event::loop;
2160#CFPlus::SDL_Quit; 2297#CFPlus::SDL_Quit;
2161#CFPlus::_exit 0; 2298#CFPlus::_exit 0;
2162 2299
2163END { 2300END {
2301 video_shutdown;
2302 audio_shutdown;
2164 CFPlus::SDL_Quit; 2303 CFPlus::SDL_Quit;
2165 CFPlus::DB::Server::stop; 2304 CFPlus::DB::Server::stop;
2166} 2305}
2167 2306
2168=head1 NAME 2307=head1 NAME
2169 2308
2170cfplus - A Crossfire+ and Crossfire game client 2309cfplus - A Crossfire TRT and Crossfire game client
2171 2310
2172=head1 SYNOPSIS 2311=head1 SYNOPSIS
2173 2312
2174Just run it - no commandline arguments are supported. 2313Just run it - no commandline arguments are supported.
2175 2314
2176=head1 USAGE 2315=head1 USAGE
2177 2316
2178cfplus utilises OpenGL for all UI elements and the game. It is supposed to be used 2317cfplus utilises OpenGL for all UI elements and the game. It is supposed to
2179fullscreen and interactively. 2318be used in fullscreen mode and interactively.
2180 2319
2181=head1 DEBUGGING 2320=head1 DEBUGGING
2182 2321
2183 2322
2184CFPLUS_DEBUG - environment variable 2323CFPLUS_DEBUG - environment variable

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines