--- deliantra/maps/perl/NPC_Dialogue.pm 2006/07/20 04:28:26 1.8 +++ deliantra/maps/perl/NPC_Dialogue.pm 2006/07/21 00:25:29 1.9 @@ -86,6 +86,16 @@ =item $match - An arrayref with previous results from C<@parse>. +=item $state - A hashref that stores state variables associated +with the NPC and the player, that is, it's values relate to the the +specific player-NPC interaction and other players will see a different +state. Useful to react to players in a stateful way. See C<@setstate> and +C<@ifstate>. + +=item $flag - A hashref that stores flags associated with the player and +can be seen by all NPCs (so better name your flags uniquely). This is +useful for storing e.g. quest information. See C<@setflag> and C<@ifflag>. + =back The environment is that standard "map scripting environment", which is @@ -95,6 +105,63 @@ Like C<@cond>, but proceed regardless of the outcome. +=item @msg perl + +Like C<@cond>, but the return value will be stringified and prepended to +the message. + +=item @setstate state value + +Sets the named state C to the given C. State values are +associated with a specific player-NPC pair, so each NPC has its own state +with respect to a particular player, which makes them useful to store +information about previous questions and possibly answers. State values +get reset whenever the NPC gets reset. + +See C<@ifstate> for an example. + +=item @ifstate state value + +Requires that the named C has the given C, otherwise this +topic is skipped. For more complex comparisons, see C<@cond> with +C<$state>. Example: + + @match quest + @setstate question quest + Do you really want to help find the magic amulet of Beeblebrox? + @match yes + @ifstate question quest + Then fetch it, stupid! + +=item @setflag flag value + +Sets the named flag C to the given C. Flag values are +associated with a specific player and can be seen by all NPCs. with +respect to a particular player, which makes them suitable to store quest +markers and other information (e.g. reputation/alignment). Flags are +persistent over the lifetime of a player, so be careful :) + +See C<@ifflag> for an example. + +=item @ifflag flag value + +Requires that the named C has the given C, otherwise this +topic is skipped. For more complex comparisons, see C<@cond> with +C<$flag>. Example: + + @match I want to do the quest! + @setflag kings_quest 1 + Then seek out Bumblebee in Navar, he will tell you... + @match I did the quest + @ifflag kings_quest 1 + Really, which quets? + +And Bumblebee might have: + + @match hi + @ifflag kings_quest + Hi, I was told you want to do the kings quest? + =item @trigger connected-id Trigger all objects with the given connected-id. The trigger is stateful @@ -114,7 +181,7 @@ my $lcmsg = lc $msg; - match: + topic: for my $match (@{ $self->{match} }) { for (split /\|/, $match->[0]) { if ($_ eq "*" || $lcmsg eq lc) { @@ -123,6 +190,19 @@ my @replies; my @match; # @match/@parse command results + + my $state = $self->{npc}{$self->{ob}->name}{dialog_state} ||= {}; + my $flag = $self->{ob}{dialog_flag} ||= {}; + + my %vars = ( + who => $self->{ob}, + npc => $self->{npc}, + state => $state, + flag => $flag, + msg => $msg, + match => \@match, + ); + local $self->{ob}{record_replies} = \@replies; # now execute @-commands (which can result in a no-match) @@ -132,16 +212,39 @@ if ($cmd eq "parse" || $cmd eq "match") { # match is future rename no re 'eval'; # default, but make sure @match = $msg =~ /$args/i - or next match; + or next topic; } elsif ($cmd eq "cond") { - cf::safe_eval $args, who => $self->{ob}, npc => $self->{npc}, msg => $msg, match => \@match - or next match; + cf::safe_eval $args, %vars + or next topic; } elsif ($cmd eq "eval") { - cf::safe_eval $args, who => $self->{ob}, npc => $self->{npc}, msg => $msg, match => \@match; + cf::safe_eval $args, %vars; warn "\@eval evaluation error: $@\n" if $@; + } elsif ($cmd eq "msg") { + push @replies, [$self->{npc}, (scalar cf::safe_eval $args, %vars)]; + + } elsif ($cmd eq "setflag") { + my ($name, $value) = split /\s+/, $args, 2; + $value ? $flag->{$name} = $value + : delete $flag->{$name}; + + } elsif ($cmd eq "setstate") { + my ($name, $value) = split /\s+/, $args, 2; + $value ? $state->{$name} = $value + : delete $state->{$name}; + + } elsif ($cmd eq "ifflag") { + my ($name, $value) = split /\s+/, $args, 2; + $flag->{$name} eq $value + or next topic; + + } elsif ($cmd eq "ifstate") { + my ($name, $value) = split /\s+/, $args, 2; + $state->{$name} eq $value + or next topic; + } elsif ($cmd eq "trigger") { my $rvalue = \$self->{npc}{dialog_trigger}{$args*1}; @@ -164,12 +267,20 @@ } elsif ($cmd eq "addtopic") { push @kw, split /\|/, $args; + $self->{add_topic}->(split /\s*\|\s*/, $args) if $self->{add_topic}; + + } elsif ($cmd eq "deltopic") { + # not yet implemented, do it out-of-band + $self->{del_topic}->(split /\s*\|\s*/, $args) if $self->{del_topic}; } else { warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->get_message . ")"; } } + delete $self->{npc}{$self->{ob}->name}{dialog_state} unless %$state; + delete $self->{ob}{dialog_flag} unless %$flag; + # combine lines into paragraphs $reply =~ s/(?<=\S)\n(?=\w)/ /g; $reply =~ s/\n\n/\n/g;