#! perl # mandatory depends=doclet our $TOPIC; our %DOCLET; our $HELP_CHANNEL = { id => "help", title => "Help", reply => "help ", tooltip => "Online Help", }; # these commands should be preferred by the client completer # we put them first in their own face. our @PREFERRED = qw(chat say shout tell); # considerable duplication between load_doclets and load_topics sub load_doclets { %DOCLET = (); my %command_list; my %preferred = map { $_ => undef } @PREFERRED; for ( [standard => "command_help"], [emote => "emote_help"], [dm => "dmcommand_help"], ) { my ($type, $path) = @$_; my $paragraphs = cf::pod::load_pod "$PODDIR/$path.pod" or die "unable to load $path"; my $level = 1e9; my $rpar; for my $par (@$paragraphs) { if ($par->{type} eq "head2") { # this code taken almost verbatim from DC/Protocol.pm if ($par->{markup} =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x) { my $cmd = $1; my @args = split /\|/, $2; @args = (".*") unless @args; $_ = $_ eq ".*" ? "" : " $_" for @args; my @variants = map "$cmd$_", sort { (length $a) <=> (length $b) } @args; $rpar = \($DOCLET{$cmd} = &cf::pod::as_cfpod ([$par])); push @{ $command_list{$type} }, @variants; $level = $par->{level}; } else { cf::error "$par->{markup}: unparsable command heading"; } } elsif ($par->{level} > $level) { $$rpar .= &cf::pod::as_cfpod ([$par]); } cf::cede_to_tick; } } cf::cede_to_tick; @$_ = grep !exists $preferred{$_}, @$_ for values %command_list; $command_list{preferred} = \@PREFERRED; while (my ($k, $v) = each %command_list) { cf::cede_to_tick; cf::client::set_command_face $k, $v; } } our $DOCLET_HANDLER = ext::doclet::register command => sub { my ($pl, $category, $command) = @_; if ($command =~ /^(cast|invoke)\s+(.*)$/) { # not used currently my ($cmd, $arg) = ($1, $2); (ext::doclet::doclet $pl, command => $cmd) . (ext::doclet::doclet $pl, spell => $arg) } elsif ($command =~ /^(ready_skill|use_skill)\s+(.*)$/) { my ($cmd, $arg) = ($1, $2); (ext::doclet::doclet $pl, command => $cmd) . (ext::doclet::doclet $pl, skill => $arg) } else { my $guard = cf::lock_acquire "ext::help::loading"; $DOCLET{$command} || "B" } }; sub load_topics($$) { my ($type, $path) = @_; my $paragraphs = cf::pod::load_pod "$PODDIR/$path.pod" or die "unable to load $path"; my @topics; my $level = 1e9; for my $par (@$paragraphs) { cf::cede_to_tick; if ($par->{type} eq "head2") { if ($par->{markup} =~ /^(\S+)/) { push @topics, $1 => [$type => $par]; $level = $par->{level}; } } elsif ($par->{level} > $level) { push @{ $topics[-1] }, $par; } } @topics } sub reload() { my $guard1 = cf::lock_acquire "ext::help::loading"; my $guard2 = cf::lock_acquire "ext::resource"; local $Coro::current->{desc} = "help loader"; $TOPIC = { (load_topics "DM Commands" => "dmcommand_help"), (load_topics "Emotes" => "emote_help"), (load_topics "Commands" => "command_help"), (load_topics "Generic Help Topics" => "generic_help"), }; load_doclets; () } cf::post_init { cf::async_ext { reload }; Coro::cede; # make sure reload acquires the lock(s) }; cf::register_command help => sub { my ($pl, $topic) = @_; if (cf::lock_active "ext::help::loading") { $pl->send_msg ($HELP_CHANNEL => "help files are being loaded currently, try again in a few seconds.", cf::NDI_REPLY | cf::NDI_CLEAR); return; } $topic = $1 if $topic =~ /(\S+)/; if (!length $topic) { # sort.. my %topics; while (my ($k, $v) = each %$TOPIC) { push @{$topics{$v->[0]}}, $k; } my $res; while (my ($k, $v) = each %topics) { $res .= "T<$k:>\n\n" . (join " ", sort @$v) . "\n\n"; } $pl->send_msg ($HELP_CHANNEL => $res, cf::NDI_REPLY | cf::NDI_CLEAR); } elsif (my $item = $TOPIC->{$topic}) { my ($type, @pars) = @$item; $pl->send_msg ($HELP_CHANNEL => (cf::pod::as_cfpod \@pars), cf::NDI_REPLY | cf::NDI_CLEAR); } else { $pl->send_msg ($HELP_CHANNEL => "'$topic' no such help topic, try just 'help' to get a list of topics.", cf::NDI_REPLY); } };