ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/NPC_Dialogue.pm
Revision: 1.3
Committed: Wed Apr 4 11:45:16 2007 UTC (17 years, 1 month ago) by elmex
Branch: MAIN
CVS Tags: rel-2_1
Changes since 1.2: +1 -1 lines
Log Message:
changed word matching in npc dialogs

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     NPC_Dialogue
4    
5     =head1 DESCRIPTION
6    
7     NPC dialogue support module.
8    
9     =cut
10    
11     package NPC_Dialogue;
12    
13     use strict;
14    
15     sub has_dialogue($) {
16     my ($ob) = @_;
17    
18     $ob->msg =~ /^\@match /;
19     }
20    
21     sub parse_message($) {
22     map [split /\n/, $_, 2],
23     grep length,
24     split /^\@match /m,
25     $_[0]
26     }
27    
28     sub new {
29     my ($class, %arg) = @_;
30    
31     my $self = bless {
32     %arg,
33     }, $class;
34    
35     $self->{match} ||= [parse_message $self->{npc}->msg];
36    
37     $self;
38     }
39    
40     sub greet {
41     my ($self) = @_;
42    
43     $self->tell ("hi")
44     }
45    
46     =item ($reply, @topics) = $dialog->tell ($msg)
47    
48     Tells the dialog object something and returns its response and optionally
49     a number of topics that are refered to by this topic.
50    
51     It supports a number of command constructs. They have to follow the
52     C<@match> directive, and there can be multiple commands that will be
53     executed in order.
54    
55     =over 4
56    
57     =item @comment text...
58    
59     A single-line comment. It will be completely ignored.
60    
61     =item @parse regex
62    
63     Parses the message using a perl regular expression (by default
64     case-insensitive). Any matches will be available as C<< $match->[$index]
65     >>.
66    
67     If the regular expression does not match, the topic is skipped.
68    
69     Example:
70    
71     @match deposit
72     @parse deposit (\d+) (\S+)
73     @eval bank::deposit $match->[0], $match->[1]
74    
75     =item @cond perl
76    
77     Evaluates the given perl code. If it returns false (or causes an
78     exception), the topic will be skipped, otherwise topic interpretation is
79     resumed.
80    
81     The following local variables are defined within the expression:
82    
83     =over 4
84    
85     =item $who - The cf::object::player object that initiated the dialogue.
86    
87     =item $npc - The NPC (or magic_ear etc.) object that is being talked to.
88    
89     =item $msg - The actual message as passed to this method.
90    
91     =item $match - An arrayref with previous results from C<@parse>.
92    
93     =item $state - A hashref that stores state variables associated
94     with the NPC and the player, that is, it's values relate to the the
95     specific player-NPC interaction and other players will see a different
96     state. Useful to react to players in a stateful way. See C<@setstate> and
97     C<@ifstate>.
98    
99     =item $flag - A hashref that stores flags associated with the player and
100     can be seen by all NPCs (so better name your flags uniquely). This is
101     useful for storing e.g. quest information. See C<@setflag> and C<@ifflag>.
102    
103     =back
104    
105     The environment is that standard "map scripting environment", which is
106     limited in the type of constructs allowed (no loops, for example).
107    
108     =item @eval perl
109    
110     Like C<@cond>, but proceed regardless of the outcome.
111    
112     =item @msg perl
113    
114     Like C<@cond>, but the return value will be stringified and prepended to
115     the message.
116    
117     =item @setstate state value
118    
119     Sets the named state C<state> to the given C<value>. State values are
120     associated with a specific player-NPC pair, so each NPC has its own state
121     with respect to a particular player, which makes them useful to store
122     information about previous questions and possibly answers. State values
123     get reset whenever the NPC gets reset.
124    
125     See C<@ifstate> for an example.
126    
127     =item @ifstate state value
128    
129     Requires that the named C<state> has the given C<value>, otherwise this
130     topic is skipped. For more complex comparisons, see C<@cond> with
131     C<$state>. Example:
132    
133     @match quest
134     @setstate question quest
135     Do you really want to help find the magic amulet of Beeblebrox?
136     @match yes
137     @ifstate question quest
138     Then fetch it, stupid!
139    
140     =item @setflag flag value
141    
142     Sets the named flag C<flag> to the given C<value>. Flag values are
143     associated with a specific player and can be seen by all NPCs. with
144     respect to a particular player, which makes them suitable to store quest
145     markers and other information (e.g. reputation/alignment). Flags are
146     persistent over the lifetime of a player, so be careful :)
147    
148     See C<@ifflag> for an example.
149    
150     =item @ifflag flag value
151    
152     Requires that the named C<flag> has the given C<value>, otherwise this
153     topic is skipped. For more complex comparisons, see C<@cond> with
154     C<$flag>. Example:
155    
156     @match I want to do the quest!
157     @setflag kings_quest 1
158     Then seek out Bumblebee in Navar, he will tell you...
159     @match I did the quest
160     @ifflag kings_quest 1
161     Really, which quets?
162    
163     And Bumblebee might have:
164    
165     @match hi
166     @ifflag kings_quest
167     Hi, I was told you want to do the kings quest?
168    
169 elmex 1.2 =item @trigger connected-id [state]
170 root 1.1
171 elmex 1.2 Trigger all objects with the given connected-id.
172    
173     When the state argument is omitted the trigger is stateful and retains an
174     internal state per connected-id. There is a limitation to the use of this: The
175     state won't be changed when the connection is triggered by other triggers. So
176     be careful when triggering the connection from other objects.
177    
178     When a state argument is given it should be either 0 or 1. 1 will 'push' the connection
179     and 0 will 'release' the connection. This is useful for example when you want to
180     let a npc control a door.
181    
182     Trigger all objects with the given connected-id by 'releasing' the connection.
183 root 1.1
184     =item @addtopic topic
185    
186     Adds the given topic names (separated by C<|>) to the list of topics
187     returned.
188    
189     =back
190    
191     =cut
192    
193     sub tell {
194     my ($self, $msg) = @_;
195    
196     my $lcmsg = lc $msg;
197    
198     topic:
199     for my $match (@{ $self->{match} }) {
200     for (split /\|/, $match->[0]) {
201 elmex 1.3 if ($_ eq "*" || $lcmsg =~ /\b\Q$_\E\b/i) {
202 root 1.1 my $reply = $match->[1];
203     my @kw;
204    
205     my @replies;
206     my @match; # @match/@parse command results
207    
208     my $state = $self->{npc}{$self->{ob}->name}{dialog_state} ||= {};
209     my $flag = $self->{ob}{dialog_flag} ||= {};
210    
211     my %vars = (
212     who => $self->{ob},
213     npc => $self->{npc},
214     state => $state,
215     flag => $flag,
216     msg => $msg,
217     match => \@match,
218     );
219    
220     local $self->{ob}{record_replies} = \@replies;
221    
222     # now execute @-commands (which can result in a no-match)
223     while ($reply =~ s/^\@(\w+)\s*([^\n]*)\n?//) {
224     my ($cmd, $args) = ($1, $2);
225    
226     if ($cmd eq "parse" || $cmd eq "match") { # match is future rename
227     no re 'eval'; # default, but make sure
228     @match = $msg =~ /$args/i
229     or next topic;
230    
231     } elsif ($cmd eq "comment") {
232     # nop
233    
234     } elsif ($cmd eq "cond") {
235     cf::safe_eval $args, %vars
236     or next topic;
237    
238     } elsif ($cmd eq "eval") {
239     cf::safe_eval $args, %vars;
240     warn "\@eval evaluation error: $@\n" if $@;
241    
242     } elsif ($cmd eq "msg") {
243     push @replies, [$self->{npc}, (scalar cf::safe_eval $args, %vars)];
244    
245     } elsif ($cmd eq "setflag") {
246     my ($name, $value) = split /\s+/, $args, 2;
247     $value ? $flag->{$name} = $value
248     : delete $flag->{$name};
249    
250     } elsif ($cmd eq "setstate") {
251     my ($name, $value) = split /\s+/, $args, 2;
252     $value ? $state->{$name} = $value
253     : delete $state->{$name};
254    
255     } elsif ($cmd eq "ifflag") {
256     my ($name, $value) = split /\s+/, $args, 2;
257     $flag->{$name} eq $value
258     or next topic;
259    
260     } elsif ($cmd eq "ifstate") {
261     my ($name, $value) = split /\s+/, $args, 2;
262     $state->{$name} eq $value
263     or next topic;
264    
265     } elsif ($cmd eq "trigger") {
266 elmex 1.2 my ($con, $state) = split /\s+/, $args, 2;
267     $con = $con * 1;
268    
269     if (defined $state) {
270     $self->{npc}->map->trigger ($args, $state);
271     } else {
272     my $rvalue = \$self->{npc}{dialog_trigger}{$con};
273     $self->{npc}->map->trigger ($con, $$rvalue = !$$rvalue);
274     }
275 root 1.1
276     } elsif ($cmd eq "addtopic") {
277     push @kw, split /\|/, $args;
278     $self->{add_topic}->(split /\s*\|\s*/, $args) if $self->{add_topic};
279    
280     } elsif ($cmd eq "deltopic") {
281     # not yet implemented, do it out-of-band
282     $self->{del_topic}->(split /\s*\|\s*/, $args) if $self->{del_topic};
283    
284     } else {
285     warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->msg . ")";
286     }
287     }
288    
289     delete $self->{npc}{$self->{ob}->name}{dialog_state} unless %$state;
290     delete $self->{ob}{dialog_flag} unless %$flag;
291    
292     # combine lines into paragraphs
293     $reply =~ s/(?<=\S)\n(?=\w)/ /g;
294     $reply =~ s/\n\n/\n/g;
295    
296     # ignores flags and npc from replies
297     $reply = join "\n", (map $_->[1], @replies), $reply;
298    
299     # now mark up all matching keywords
300     for my $match (@{ $self->{match} }) {
301     for (sort { (length $b) <=> (length $a) } split /\|/, $match->[0]) {
302     if ($reply =~ /\b\Q$_\E\b/i) {
303     push @kw, $_;
304     last;
305     }
306     }
307     }
308    
309     return wantarray ? ($reply, @kw) : $reply;
310     }
311     }
312     }
313    
314     ()
315     }
316    
317     1