ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
(Generate patch)

Comparing deliantra/server/lib/cf.pm (file contents):
Revision 1.597 by root, Sun Nov 11 02:14:37 2012 UTC vs.
Revision 1.614 by root, Mon Nov 26 15:12:16 2012 UTC

58use BDB (); 58use BDB ();
59use Data::Dumper; 59use Data::Dumper;
60use Fcntl; 60use Fcntl;
61use YAML::XS (); 61use YAML::XS ();
62use IO::AIO (); 62use IO::AIO ();
63use Time::HiRes;
64use Compress::LZF; 63use Compress::LZF;
65use Digest::MD5 (); 64use Digest::MD5 ();
66 65
67AnyEvent::detect; 66AnyEvent::detect;
68 67
252Configuration for the server, loaded from C</etc/deliantra-server/config>, or 251Configuration for the server, loaded from C</etc/deliantra-server/config>, or
253from wherever your confdir points to. 252from wherever your confdir points to.
254 253
255=item cf::wait_for_tick, cf::wait_for_tick_begin 254=item cf::wait_for_tick, cf::wait_for_tick_begin
256 255
257These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only 256These are functions that inhibit the current coroutine one tick.
258returns directly I<after> the tick processing (and consequently, can only wake one thread 257cf::wait_for_tick_begin only returns directly I<after> the tick
258processing (and consequently, can only wake one thread per tick), while
259per tick), while cf::wait_for_tick wakes up all waiters after tick processing. 259cf::wait_for_tick wakes up all waiters after tick processing.
260 260
261Note that cf::Wait_for_tick will immediately return when the server is not 261Note that cf::wait_for_tick will immediately return when the server is not
262ticking, making it suitable for small pauses in threads that need to run 262ticking, making it suitable for small pauses in threads that need to run
263when the server is paused. If that is not applicable (i.e. you I<really> 263when the server is paused. If that is not applicable (i.e. you I<really>
264want to wait, use C<$cf::WAIT_FOR_TICK>). 264want to wait, use C<$cf::WAIT_FOR_TICK>).
265 265
266=item $cf::WAIT_FOR_TICK 266=item $cf::WAIT_FOR_TICK
573Allocate $time seconds of blocking CPU time at priority C<$priority> 573Allocate $time seconds of blocking CPU time at priority C<$priority>
574(default: 0): This call blocks and returns only when you have at least 574(default: 0): This call blocks and returns only when you have at least
575C<$time> seconds of cpu time till the next tick. The slot is only valid 575C<$time> seconds of cpu time till the next tick. The slot is only valid
576till the next cede. 576till the next cede.
577 577
578Background jobs should use a priority les than zero, interactive jobs 578Background jobs should use a priority less than zero, interactive jobs
579should use 100 or more. 579should use 100 or more.
580 580
581The optional C<$name> can be used to identify the job to run. It might be 581The optional C<$name> can be used to identify the job to run. It might be
582used for statistical purposes and should identify the same time-class. 582used for statistical purposes and should identify the same time-class.
583 583
596 my $signal = new Coro::Signal; 596 my $signal = new Coro::Signal;
597 my $busy; 597 my $busy;
598 598
599 while () { 599 while () {
600 next_job: 600 next_job:
601
602 Coro::cede;
601 603
602 my $avail = cf::till_tick; 604 my $avail = cf::till_tick;
603 605
604 for (0 .. $#SLOT_QUEUE) { 606 for (0 .. $#SLOT_QUEUE) {
605 if ($SLOT_QUEUE[$_][0] <= $avail) { 607 if ($SLOT_QUEUE[$_][0] <= $avail) {
606 $busy = 0; 608 $busy = 0;
607 my $job = splice @SLOT_QUEUE, $_, 1, (); 609 my $job = splice @SLOT_QUEUE, $_, 1, ();
608 $job->[2]->send; 610 $job->[2]->send;
609 Coro::cede;
610 goto next_job; 611 goto next_job;
611 } else { 612 } else {
612 $SLOT_QUEUE[$_][0] *= $SLOT_DECAY; 613 $SLOT_QUEUE[$_][0] *= $SLOT_DECAY;
613 } 614 }
614 } 615 }
615 616
616 if (@SLOT_QUEUE) { 617 if (@SLOT_QUEUE) {
617 # we do not use wait_for_tick() as it returns immediately when tick is inactive 618 wait_for_tick;
618 $WAIT_FOR_TICK->wait;
619 } else { 619 } else {
620 $busy = 0; 620 $busy = 0;
621 Coro::schedule; 621 Coro::schedule;
622 } 622 }
623 } 623 }
738 reset_signals; 738 reset_signals;
739} 739}
740 740
741sub fork_call(&@) { 741sub fork_call(&@) {
742 my ($cb, @args) = @_; 742 my ($cb, @args) = @_;
743
744 # we seemingly have to make a local copy of the whole thing,
745 # otherwise perl prematurely frees the stuff :/
746 # TODO: investigate and fix (likely this will be rather laborious)
747 743
748 my @res = Coro::Util::fork_eval { 744 my @res = Coro::Util::fork_eval {
749 cf::post_fork; 745 cf::post_fork;
750 &$cb 746 &$cb
751 } @args; 747 } @args;
901 897
902 return db_get cache => "$id/data"; 898 return db_get cache => "$id/data";
903 } 899 }
904 } 900 }
905 901
906 my $t1 = Time::HiRes::time; 902 my $t1 = EV::time;
907 my $data = $process->(\@data); 903 my $data = $process->(\@data);
908 my $t2 = Time::HiRes::time; 904 my $t2 = EV::time;
909 905
910 info "cache: '$id' processed in ", $t2 - $t1, "s\n"; 906 info "cache: '$id' processed in ", $t2 - $t1, "s\n";
911 907
912 db_put cache => "$id/data", $data; 908 db_put cache => "$id/data", $data;
913 db_put cache => "$id/md5" , $md5; 909 db_put cache => "$id/md5" , $md5;
2654 2650
2655Creates and returns a persistent reference to an object that can be stored as a string. 2651Creates and returns a persistent reference to an object that can be stored as a string.
2656 2652
2657=item $ob = cf::object::deref ($refstring) 2653=item $ob = cf::object::deref ($refstring)
2658 2654
2659returns the objetc referenced by refstring. may return undef when it cnanot find the object, 2655returns the objetc referenced by refstring. may return undef when it cannot find the object,
2660even if the object actually exists. May block. 2656even if the object actually exists. May block.
2661 2657
2662=cut 2658=cut
2663 2659
2664sub deref { 2660sub deref {
2746=item $player_object->may ("access") 2742=item $player_object->may ("access")
2747 2743
2748Returns wether the given player is authorized to access resource "access" 2744Returns wether the given player is authorized to access resource "access"
2749(e.g. "command_wizcast"). 2745(e.g. "command_wizcast").
2750 2746
2747This is implemented by checking a config setting of C<may_access> where
2748C<access> is replaced by the access string. The following alternatives are
2749possible (and are tested in order):
2750
2751=over 4
2752
2753=item * Player is DM
2754
2755The request will succeed.
2756
2757=item * may_access is an array reference
2758
2759If either the player nickname or UUID is in the array, the request will
2760succeed, otherwise it will fail.
2761
2762=item * may_access is a true value
2763
2764The request will succeed.
2765
2766=item * may_access is missing or false
2767
2768The request will fail.
2769
2770=back
2771
2751=cut 2772=cut
2752 2773
2753sub cf::object::player::may { 2774sub cf::object::player::may {
2754 my ($self, $access) = @_; 2775 my ($self, $access) = @_;
2755 2776
2756 $self->flag (cf::FLAG_WIZ) || 2777 $self->flag (cf::FLAG_WIZ) ||
2757 (ref $cf::CFG{"may_$access"} 2778 (ref $cf::CFG{"may_$access"}
2758 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 2779 ? scalar grep $self->name eq $_ || $self->uuid eq $_, @{$cf::CFG{"may_$access"}}
2759 : $cf::CFG{"may_$access"}) 2780 : $cf::CFG{"may_$access"})
2760} 2781}
2761 2782
2762=item $player_object->enter_link 2783=item $player_object->enter_link
2763 2784
3046 3067
3047=head3 cf::client 3068=head3 cf::client
3048 3069
3049=over 4 3070=over 4
3050 3071
3051=item $client->send_drawinfo ($text, $flags)
3052
3053Sends a drawinfo packet to the client. Circumvents output buffering so
3054should not be used under normal circumstances.
3055
3056=cut
3057
3058sub cf::client::send_drawinfo {
3059 my ($self, $text, $flags) = @_;
3060
3061 utf8::encode $text;
3062 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
3063}
3064
3065=item $client->send_big_packet ($pkt) 3072=item $client->send_big_packet ($pkt)
3066 3073
3067Like C<send_packet>, but tries to compress large packets, and fragments 3074Like C<send_packet>, but tries to compress large packets, and fragments
3068them as required. 3075them as required.
3069 3076
3087 $self->send_packet ($pkt); 3094 $self->send_packet ($pkt);
3088} 3095}
3089 3096
3090=item $client->send_msg ($channel, $msg, $color, [extra...]) 3097=item $client->send_msg ($channel, $msg, $color, [extra...])
3091 3098
3092Send a drawinfo or msg packet to the client, formatting the msg for the 3099Send a msg packet to the client, formatting the msg for the client if
3093client if neccessary. C<$type> should be a string identifying the type of 3100necessary. C<$type> should be a string identifying the type of the
3094the message, with C<log> being the default. If C<$color> is negative, suppress 3101message, with C<log> being the default. If C<$color> is negative, suppress
3095the message unless the client supports the msg packet. 3102the message unless the client supports the msg packet.
3096 3103
3097=cut 3104=cut
3098 3105
3099# non-persistent channels (usually the info channel) 3106# non-persistent channels (usually the info channel)
3204 id => "death", 3211 id => "death",
3205 title => "Death", 3212 title => "Death",
3206 reply => undef, 3213 reply => undef,
3207 tooltip => "Reason for and more info about your most recent death", 3214 tooltip => "Reason for and more info about your most recent death",
3208 }, 3215 },
3216 "c/fatal" => {
3217 id => "fatal",
3218 title => "Fatal Error",
3219 reply => undef,
3220 tooltip => "Reason for the server disconnect",
3221 },
3209 "c/say" => $SAY_CHANNEL, 3222 "c/say" => $SAY_CHANNEL,
3210 "c/chat" => $CHAT_CHANNEL, 3223 "c/chat" => $CHAT_CHANNEL,
3211); 3224);
3212 3225
3213sub cf::client::send_msg { 3226sub cf::client::send_msg {
3286 3299
3287 $self->send_packet ($self->{query_queue}[0][0]) 3300 $self->send_packet ($self->{query_queue}[0][0])
3288 if @{ $self->{query_queue} } == 1; 3301 if @{ $self->{query_queue} } == 1;
3289 3302
3290 1 3303 1
3304}
3305
3306=item $client->update_command_faces
3307
3308=cut
3309
3310our %COMMAND_FACE;
3311
3312sub cf::client::update_command_faces {
3313 my ($self) = @_;
3314
3315 my @faces = grep $_,
3316 $COMMAND_FACE{preferred},
3317 $COMMAND_FACE{standard},
3318 $COMMAND_FACE{skill},
3319 $self->pl->ob->flag (cf::FLAG_WIZ) ? $COMMAND_FACE{dm} : (),
3320 $COMMAND_FACE{emote},
3321 ;
3322
3323 $self->send_face ($_)
3324 for @faces;
3325 $self->flush_fx;
3326
3327 $self->ext_msg (command_list => @faces);
3328}
3329
3330=item cf::client::set_command_face $type, $commands
3331
3332=cut
3333
3334sub cf::client::set_command_face {
3335 my ($type, $list) = @_;
3336
3337 my $idx = &cf::face::set ( #d# ugly forward reference
3338 "command_list/$type" => cf::FT_RSRC,
3339 JSON::XS->new->utf8->encode ([ sort @$list ])
3340 );
3341
3342 $COMMAND_FACE{$type} = $idx;
3291} 3343}
3292 3344
3293cf::client->attach ( 3345cf::client->attach (
3294 on_connect => sub { 3346 on_connect => sub {
3295 my ($ns) = @_; 3347 my ($ns) = @_;
3519=cut 3571=cut
3520 3572
3521############################################################################# 3573#############################################################################
3522# the server's init and main functions 3574# the server's init and main functions
3523 3575
3524our %FACEHASH; # hash => idx, #d# HACK for http server 3576{
3577 package cf::face;
3525 3578
3579 our %HASH; # hash => idx
3580 our @DATA; # dynamically-created facedata, only faceste 0 used
3581 our @FOFS; # file offset, if > 0
3582 our @SIZE; # size of face, in octets
3583 our @META; # meta hash of face, if any
3584 our $DATAFH; # facedata filehandle
3585
3526# internal api, not fianlised 3586 # internal api, not finalised
3527sub set_face { 3587 sub set {
3528 my ($name, $type, $data) = @_; 3588 my ($name, $type, $data) = @_;
3529 3589
3530 my $idx = cf::face::find $name; 3590 my $idx = cf::face::find $name;
3531 3591
3532 if ($idx) { 3592 if ($idx) {
3533 delete $FACEHASH{cf::face::get_chksum $idx}; 3593 delete $HASH{cf::face::get_csum $idx};
3534 } else { 3594 } else {
3535 $idx = cf::face::alloc $name; 3595 $idx = cf::face::alloc $name;
3536 } 3596 }
3537 3597
3538 my $hash = cf::face::mangle_chksum Digest::MD5::md5 $data; 3598 my $hash = cf::face::mangle_csum Digest::MD5::md5 $data;
3539 3599
3540 cf::face::set_type $idx, $type; 3600 cf::face::set_type $idx, $type;
3541 cf::face::set_data $idx, 0, $data, $hash; 3601 cf::face::set_csum $idx, 0, $hash;
3542 cf::face::set_meta $idx, $type & 1 ? undef : undef; 3602
3603 # we need to destroy the SV itself, not just modify it, as a running ix
3604 # might hold a reference to it: "delete" achieves that.
3605 delete $FOFS[0][$idx];
3606 delete $DATA[0][$idx];
3607 $DATA[0][$idx] = $data;
3608 $SIZE[0][$idx] = length $data;
3609 delete $META[$idx];
3543 $FACEHASH{$hash} = $idx;#d# 3610 $HASH{$hash} = $idx;#d#
3544 3611
3545 $idx 3612 $idx
3613 }
3614
3615 sub _get_data($$$) {
3616 my ($idx, $set, $cb) = @_;
3617
3618 if (defined $DATA[$set][$idx]) {
3619 $cb->($DATA[$set][$idx]);
3620 } elsif (my $fofs = $FOFS[$set][$idx]) {
3621 my $size = $SIZE[$set][$idx];
3622 my $buf;
3623 IO::AIO::aio_read $DATAFH, $fofs, $size, $buf, 0, sub {
3624 if ($_[0] == $size) {
3625 #cf::debug "read face $idx, $size from $fofs as ", length $buf;#d#
3626 $cb->($buf);
3627 } else {
3628 cf::error "INTERNAL ERROR: unable to read facedata for face $idx#$set ($size, $fofs), ignoring request.";
3629 }
3630 };
3631 } else {
3632 cf::error "requested facedata for unknown face $idx#$set, ignoring.";
3633 }
3634 }
3635
3636 # rather ineffient
3637 sub cf::face::get_data($;$) {
3638 my ($idx, $set) = @_;
3639
3640 _get_data $idx, $set, Coro::rouse_cb;
3641 Coro::rouse_wait
3642 }
3643
3644 sub cf::face::ix {
3645 my ($ns, $set, $idx, $pri) = @_;
3646
3647 _get_data $idx, $set, sub {
3648 $ns->ix_send ($idx, $pri, $_[0]);
3649 };
3650 }
3546} 3651}
3547 3652
3548sub load_facedata($) { 3653sub load_facedata($) {
3549 my ($path) = @_; 3654 my ($path) = @_;
3550 3655
3551 # HACK to clear player env face cache, we need some signal framework
3552 # for this (global event?)
3553 %ext::player_env::MUSIC_FACE_CACHE = ();
3554
3555 my $enc = JSON::XS->new->utf8->canonical->relaxed; 3656 my $enc = JSON::XS->new->utf8->canonical->relaxed;
3556 3657
3557 trace "loading facedata from $path\n"; 3658 trace "loading facedata from $path\n";
3558 3659
3559 my $facedata = decode_storable load_file $path; 3660 my $facedata = decode_storable load_file "$path/faceinfo";
3560 3661
3561 $facedata->{version} == 2 3662 $facedata->{version} == 2
3562 or cf::cleanup "$path: version mismatch, cannot proceed."; 3663 or cf::cleanup "$path/faceinfo: version mismatch, cannot proceed.";
3563 3664
3564 cf::cede_to_tick; 3665 my $fh = aio_open "$DATADIR/facedata", IO::AIO::O_RDONLY, 0
3666 or cf::cleanup "$path/facedata: $!, cannot proceed.";
3667
3668 get_slot 1, -100, "load_facedata"; # make sure we get a very big slot
3669
3670 # BEGIN ATOMIC
3671 # from here on, everything must be atomic - no thread switch allowed
3672 my $t1 = EV::time;
3565 3673
3566 { 3674 {
3567 my $faces = delete $facedata->{faceinfo}; 3675 my $faces = $facedata->{faceinfo};
3568 3676
3569 for my $face (sort keys %$faces) { 3677 for my $face (sort keys %$faces) {
3570 my $info = $faces->{$face}; 3678 my $info = $faces->{$face};
3571 my $idx = (cf::face::find $face) || cf::face::alloc $face; 3679 my $idx = (cf::face::find $face) || cf::face::alloc $face;
3572 3680
3573 cf::face::set_visibility $idx, $info->{visibility}; 3681 cf::face::set_visibility $idx, $info->{visibility};
3574 cf::face::set_magicmap $idx, $info->{magicmap}; 3682 cf::face::set_magicmap $idx, $info->{magicmap};
3575 cf::face::set_data $idx, 0, $info->{data32}, $info->{hash32}; 3683 cf::face::set_csum $idx, 0, $info->{hash64}; $cf::face::SIZE[0][$idx] = $info->{size64}; $cf::face::FOFS[0][$idx] = $info->{fofs64};
3576 cf::face::set_data $idx, 1, $info->{data64}, $info->{hash64}; 3684 cf::face::set_csum $idx, 1, $info->{hash32}; $cf::face::SIZE[1][$idx] = $info->{size32}; $cf::face::FOFS[1][$idx] = $info->{fofs32};
3577 cf::face::set_data $idx, 2, $info->{glyph} , $info->{glyph} ; 3685 cf::face::set_csum $idx, 2, $info->{glyph}; $cf::face::DATA[2][$idx] = $info->{glyph};
3578 $FACEHASH{$info->{hash64}} = $idx;#d# 3686 $cf::face::HASH{$info->{hash64}} = $idx;
3579 3687 delete $cf::face::META[$idx];
3580 cf::cede_to_tick;
3581 } 3688 }
3582 3689
3583 while (my ($face, $info) = each %$faces) { 3690 while (my ($face, $info) = each %$faces) {
3584 next unless $info->{smooth}; 3691 next unless $info->{smooth};
3585 3692
3590 cf::face::set_smooth $idx, $smooth; 3697 cf::face::set_smooth $idx, $smooth;
3591 cf::face::set_smoothlevel $idx, $info->{smoothlevel}; 3698 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
3592 } else { 3699 } else {
3593 error "smooth face '$info->{smooth}' not found for face '$face'"; 3700 error "smooth face '$info->{smooth}' not found for face '$face'";
3594 } 3701 }
3595
3596 cf::cede_to_tick;
3597 } 3702 }
3598 } 3703 }
3599 3704
3600 { 3705 {
3601 my $anims = delete $facedata->{animinfo}; 3706 my $anims = $facedata->{animinfo};
3602 3707
3603 while (my ($anim, $info) = each %$anims) { 3708 while (my ($anim, $info) = each %$anims) {
3604 cf::anim::set $anim, $info->{frames}, $info->{facings}; 3709 cf::anim::set $anim, $info->{frames}, $info->{facings};
3605 cf::cede_to_tick;
3606 } 3710 }
3607 3711
3608 cf::anim::invalidate_all; # d'oh 3712 cf::anim::invalidate_all; # d'oh
3609 } 3713 }
3610 3714
3611 { 3715 {
3612 my $res = delete $facedata->{resource}; 3716 my $res = $facedata->{resource};
3613 3717
3614 while (my ($name, $info) = each %$res) { 3718 while (my ($name, $info) = each %$res) {
3615 if (defined (my $type = $info->{type})) { 3719 if (defined (my $type = $info->{type})) {
3616 # TODO: different hash - must free and use new index, or cache ixface data queue 3720 # TODO: different hash - must free and use new index, or cache ixface data queue
3617 my $idx = (cf::face::find $name) || cf::face::alloc $name; 3721 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3618 3722
3619 cf::face::set_type $idx, $type; 3723 cf::face::set_type $idx, $type;
3620 cf::face::set_data $idx, 0, $info->{data}, $info->{hash}; 3724 cf::face::set_csum $idx, 0, $info->{hash};
3725 $cf::face::SIZE[0][$idx] = $info->{size};
3726 $cf::face::FOFS[0][$idx] = $info->{fofs};
3621 cf::face::set_meta $idx, $type & 1 ? undef : $info->{meta}; # preserve meta unless prepended already 3727 $cf::face::META[$idx] = $type & 1 ? undef : $info->{meta}; # preserve meta unless prepended already
3622 $FACEHASH{$info->{hash}} = $idx;#d# 3728 $cf::face::HASH{$info->{hash}} = $idx;
3623 } else { 3729 } else {
3624# $RESOURCE{$name} = $info; # unused 3730# $RESOURCE{$name} = $info; # unused
3625 } 3731 }
3626
3627 cf::cede_to_tick;
3628 } 3732 }
3629 } 3733 }
3734
3735 ($fh, $cf::face::DATAFH) = ($cf::face::DATAFH, $fh);
3736
3737 # HACK to clear player env face cache, we need some signal framework
3738 # for this (global event?)
3739 %ext::player_env::MUSIC_FACE_CACHE = ();
3740
3741 # END ATOMIC
3742
3743 cf::debug "facedata atomic update time ", EV::time - $t1;
3630 3744
3631 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE); 3745 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE);
3746
3747 aio_close $fh if $fh; # close old facedata
3632 3748
3633 1 3749 1
3634} 3750}
3635 3751
3636register_exticmd fx_want => sub { 3752register_exticmd fx_want => sub {
3652} 3768}
3653 3769
3654sub reload_exp_table { 3770sub reload_exp_table {
3655 _reload_exp_table; 3771 _reload_exp_table;
3656 3772
3773 cf::face::set
3657 set_face "res/exp_table" => FT_RSRC, 3774 "res/exp_table" => FT_RSRC,
3658 JSON::XS->new->utf8->canonical->encode ( 3775 JSON::XS->new->utf8->canonical->encode (
3659 [map cf::level_to_min_exp $_, 1 .. cf::settings->max_level] 3776 [map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]
3660 ); 3777 );
3661} 3778}
3662 3779
3663sub reload_materials { 3780sub reload_materials {
3664 _reload_materials; 3781 _reload_materials;
3665} 3782}
3677 if exists $_->{match}; 3794 if exists $_->{match};
3678 } 3795 }
3679} 3796}
3680 3797
3681sub reload_facedata { 3798sub reload_facedata {
3682 load_facedata "$DATADIR/facedata" 3799 load_facedata $DATADIR
3683 or die "unable to load facedata\n"; 3800 or die "unable to load facedata\n";
3684} 3801}
3685 3802
3686sub reload_archetypes { 3803sub reload_archetypes {
3687 load_resource_file "$DATADIR/archetypes" 3804 load_resource_file "$DATADIR/archetypes"
3688 or die "unable to load archetypes\n"; 3805 or die "unable to load archetypes\n";
3689 3806
3807 cf::face::set
3690 set_face "res/skill_info" => FT_RSRC, 3808 "res/skill_info" => FT_RSRC,
3691 JSON::XS->new->utf8->canonical->encode ( 3809 JSON::XS->new->utf8->canonical->encode (
3692 [map [cf::arch::skillvec ($_)->name], 0 .. cf::arch::skillvec_size - 1] 3810 [map [cf::arch::skillvec ($_)->name], 0 .. cf::arch::skillvec_size - 1]
3693 ); 3811 );
3812
3813 cf::face::set
3694 set_face "res/spell_paths" => FT_RSRC, 3814 "res/spell_paths" => FT_RSRC,
3695 JSON::XS->new->utf8->canonical->encode ( 3815 JSON::XS->new->utf8->canonical->encode (
3696 [map [cf::spellpathnames ($_)], 0 .. NRSPELLPATHS - 1] 3816 [map [cf::spellpathnames ($_)], 0 .. NRSPELLPATHS - 1]
3697 ); 3817 );
3818
3819 # command completion
3820 my @commands;
3821
3822 for (0..cf::arch::skillvec_size - 1) {
3823 my $skill = cf::arch::skillvec $_;
3824 my $name = $skill->name;
3825 my $flags = cf::skill_flags $skill->subtype;
3826
3827 push @commands, "ready_skill $name" if $flags & (SF_COMBAT | SF_RANGED | SF_GRACE);
3828 push @commands, "use_skill $name" if $flags & (SF_USE | SF_AUTARK | SF_GRACE);
3829 }
3830
3831 cf::client::set_command_face skill => \@commands;
3698} 3832}
3699 3833
3700sub reload_treasures { 3834sub reload_treasures {
3701 load_resource_file "$DATADIR/treasures" 3835 load_resource_file "$DATADIR/treasures"
3702 or die "unable to load treasurelists\n"; 3836 or die "unable to load treasurelists\n";

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines