ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/NPC_Dialogue.pm
Revision: 1.22
Committed: Tue May 4 22:49:21 2010 UTC (14 years ago) by root
Branch: MAIN
CVS Tags: rel-3_1, rel-3_0, HEAD
Changes since 1.21: +1 -1 lines
Log Message:
more common sense

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 root 1.5 =over 4
10    
11 root 1.1 =cut
12    
13     package NPC_Dialogue;
14    
15 root 1.22 use common::sense;
16 root 1.1
17     sub parse_message($) {
18     map [split /\n/, $_, 2],
19     grep length,
20     split /^\@match /m,
21     $_[0]
22     }
23    
24     sub new {
25     my ($class, %arg) = @_;
26    
27 root 1.4 $arg{ob} = $arg{pl}->ob;
28    
29 root 1.1 my $self = bless {
30     %arg,
31     }, $class;
32    
33     $self->{match} ||= [parse_message $self->{npc}->msg];
34    
35     $self;
36     }
37    
38     sub greet {
39     my ($self) = @_;
40    
41     $self->tell ("hi")
42     }
43    
44     =item ($reply, @topics) = $dialog->tell ($msg)
45    
46     Tells the dialog object something and returns its response and optionally
47     a number of topics that are refered to by this topic.
48    
49     It supports a number of command constructs. They have to follow the
50     C<@match> directive, and there can be multiple commands that will be
51     executed in order.
52    
53     =over 4
54    
55     =item @comment text...
56    
57     A single-line comment. It will be completely ignored.
58    
59     =item @parse regex
60    
61     Parses the message using a perl regular expression (by default
62     case-insensitive). Any matches will be available as C<< $match->[$index]
63     >>.
64    
65     If the regular expression does not match, the topic is skipped.
66    
67     Example:
68    
69     @match deposit
70     @parse deposit (\d+) (\S+)
71     @eval bank::deposit $match->[0], $match->[1]
72    
73     =item @cond perl
74    
75     Evaluates the given perl code. If it returns false (or causes an
76     exception), the topic will be skipped, otherwise topic interpretation is
77     resumed.
78    
79     The following local variables are defined within the expression:
80    
81     =over 4
82    
83     =item $who - The cf::object::player object that initiated the dialogue.
84    
85     =item $npc - The NPC (or magic_ear etc.) object that is being talked to.
86    
87 root 1.21 =item $map - The map the NPC (not the player) is on.
88    
89 root 1.1 =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 root 1.18 =item $find - see @find, below.
104 root 1.15
105 root 1.1 =back
106    
107     The environment is that standard "map scripting environment", which is
108     limited in the type of constructs allowed (no loops, for example).
109    
110 elmex 1.6 Here is a example:
111    
112     =over 4
113    
114     =item B<matching for an item name>
115    
116     @match hi
117     @cond grep $_->name =~ /royalty/, $who->inv
118     You got royalties there! Wanna have!
119    
120     You may want to change the C<name> method there to something like C<title>,
121     C<slaying> or any other method that is allowed to be called on a
122     C<cf::object> here.
123    
124 elmex 1.7 =item B<matching for an item name and removing the matched item>
125    
126     @match found earhorn
127     @cond grep $_->slaying =~ /Gramp's walking stick/, $who->inv
128 root 1.9 @eval my @g = grep { $_->slaying =~ /Gramp's walking stick/ } $who->inv; $g[0]->decrease;
129 elmex 1.7 Thanks for the earhorn!
130    
131     This example is a bit more complex. The C<@eval> statement will search
132     the players inventory for the same term as the C<@cond> and then
133     decreases the number of objects used there.
134    
135     (See also the map: C<scorn/houses/cornerbrook.map> for an example how this is
136     used in the real world :-)
137    
138 elmex 1.6 =back
139    
140 root 1.1 =item @eval perl
141    
142     Like C<@cond>, but proceed regardless of the outcome.
143    
144     =item @msg perl
145    
146     Like C<@cond>, but the return value will be stringified and prepended to
147 root 1.15 the reply message.
148    
149     =item @check match expression
150    
151     Executes a match expression (see
152     http://pod.tst.eu/http://cvs.schmorp.de/deliantra/server/lib/cf/match.pm)
153     to see if it matches.
154    
155     C<self> is the npc object, C<object>, C<source> and C<originator> are the
156     player communicating with the NPC.
157    
158     If the check fails, the match is skipped.
159    
160     =item @find match expression
161    
162     Like C<@check> in that it executes a match expression, but instead of
163 root 1.18 failing, it gathers all objects into an array and provides a reference to
164     the array in the C<$find> variable.
165 root 1.15
166     When you want to skip the match when no objects have been found, combine
167     C<@find> with C<@cond>:
168    
169     @match see my spellbook
170     @find type=SPELLBOOK in inv
171 root 1.18 @cond @$find
172 root 1.15 It looks dirty.
173     @match see my spellbook
174     I can't see any, where do you have it?
175 root 1.1
176     =item @setstate state value
177    
178     Sets the named state C<state> to the given C<value>. State values are
179     associated with a specific player-NPC pair, so each NPC has its own state
180     with respect to a particular player, which makes them useful to store
181     information about previous questions and possibly answers. State values
182     get reset whenever the NPC gets reset.
183    
184     See C<@ifstate> for an example.
185    
186     =item @ifstate state value
187    
188     Requires that the named C<state> has the given C<value>, otherwise this
189 root 1.20 topic is skipped. For more complex comparisons, see C<@cond> with
190 root 1.1 C<$state>. Example:
191    
192     @match quest
193     @setstate question quest
194     Do you really want to help find the magic amulet of Beeblebrox?
195     @match yes
196     @ifstate question quest
197     Then fetch it, stupid!
198    
199     =item @setflag flag value
200    
201     Sets the named flag C<flag> to the given C<value>. Flag values are
202     associated with a specific player and can be seen by all NPCs. with
203     respect to a particular player, which makes them suitable to store quest
204     markers and other information (e.g. reputation/alignment). Flags are
205     persistent over the lifetime of a player, so be careful :)
206    
207 root 1.16 Perversely enough, using C<@setfflag> without a C<value> clears the flag
208     as if it was never set, so always provide a flag value (e.g. C<1>) when
209     you want to set the flag.
210    
211 root 1.1 See C<@ifflag> for an example.
212    
213     =item @ifflag flag value
214    
215     Requires that the named C<flag> has the given C<value>, otherwise this
216 root 1.16 topic is skipped. For more complex comparisons, see C<@cond> with
217     C<$flag>.
218    
219     If no C<value> is given, then the ifflag succeeds when the flag is true.
220    
221     Example:
222 root 1.1
223     @match I want to do the quest!
224     @setflag kings_quest 1
225     Then seek out Bumblebee in Navar, he will tell you...
226     @match I did the quest
227     @ifflag kings_quest 1
228     Really, which quets?
229    
230     And Bumblebee might have:
231    
232     @match hi
233     @ifflag kings_quest
234     Hi, I was told you want to do the kings quest?
235    
236 elmex 1.2 =item @trigger connected-id [state]
237 root 1.1
238 elmex 1.2 Trigger all objects with the given connected-id.
239    
240     When the state argument is omitted the trigger is stateful and retains an
241     internal state per connected-id. There is a limitation to the use of this: The
242     state won't be changed when the connection is triggered by other triggers. So
243     be careful when triggering the connection from other objects.
244    
245 root 1.14 When a state argument is given it should be a positive integer. Any value
246     C<!= 0> will 'push' the connection (in general, you should specify C<1>
247     for this) and C<0> will 'release' the connection. This is useful for
248     example when you want to let an NPC control a door.
249 elmex 1.2
250     Trigger all objects with the given connected-id by 'releasing' the connection.
251 root 1.1
252 root 1.8 =item @playersound face-name
253    
254     Plays the given sound face (either an alias or sound file path) so that
255     only the player talking to the npc can hear it.
256    
257     =item @npcsound face-name
258    
259     Plays the given sound face (either an alias or sound file path) as if
260     the npc had made that sound, i.e. it will be located at the npc and all
261     players near enough can hear it.
262    
263 root 1.1 =item @addtopic topic
264    
265     Adds the given topic names (separated by C<|>) to the list of topics
266     returned.
267    
268     =back
269    
270     =cut
271    
272     sub tell {
273     my ($self, $msg) = @_;
274    
275     my $lcmsg = lc $msg;
276    
277     topic:
278     for my $match (@{ $self->{match} }) {
279     for (split /\|/, $match->[0]) {
280 elmex 1.3 if ($_ eq "*" || $lcmsg =~ /\b\Q$_\E\b/i) {
281 root 1.1 my $reply = $match->[1];
282     my @kw;
283    
284     my @replies;
285     my @match; # @match/@parse command results
286    
287     my $state = $self->{npc}{$self->{ob}->name}{dialog_state} ||= {};
288     my $flag = $self->{ob}{dialog_flag} ||= {};
289    
290 root 1.15 my @find;
291    
292 root 1.1 my %vars = (
293     who => $self->{ob},
294     npc => $self->{npc},
295 root 1.21 map => $self->{npc}->map,
296 root 1.1 state => $state,
297     flag => $flag,
298     msg => $msg,
299     match => \@match,
300 root 1.18 find => \@find,
301 root 1.1 );
302    
303     local $self->{ob}{record_replies} = \@replies;
304    
305     # now execute @-commands (which can result in a no-match)
306     while ($reply =~ s/^\@(\w+)\s*([^\n]*)\n?//) {
307     my ($cmd, $args) = ($1, $2);
308    
309     if ($cmd eq "parse" || $cmd eq "match") { # match is future rename
310     no re 'eval'; # default, but make sure
311     @match = $msg =~ /$args/i
312     or next topic;
313    
314     } elsif ($cmd eq "comment") {
315     # nop
316    
317 root 1.8 } elsif ($cmd eq "playersound") {
318     $self->{ob}->contr->play_sound (cf::sound::find $args);
319    
320     } elsif ($cmd eq "npcsound") {
321     $self->{npc}->play_sound (cf::sound::find $args);
322    
323 root 1.1 } elsif ($cmd eq "cond") {
324     cf::safe_eval $args, %vars
325     or next topic;
326    
327     } elsif ($cmd eq "eval") {
328     cf::safe_eval $args, %vars;
329     warn "\@eval evaluation error: $@\n" if $@;
330    
331 root 1.15 } elsif ($cmd eq "check") {
332     eval {
333     cf::match::match $args, $self->{ob}, $self->{npc}, $self->{ob}
334     or next topic;
335     };
336     warn "\@check evaluation error: $@\n" if $@;
337    
338     } elsif ($cmd eq "find") {
339     @find = eval {
340     cf::match::match $args, $self->{ob}, $self->{npc}, $self->{ob}
341     };
342     warn "\@find evaluation error: $@\n" if $@;
343    
344 root 1.1 } elsif ($cmd eq "msg") {
345     push @replies, [$self->{npc}, (scalar cf::safe_eval $args, %vars)];
346    
347     } elsif ($cmd eq "setflag") {
348     my ($name, $value) = split /\s+/, $args, 2;
349 root 1.19 defined $value ? $flag->{$name} = $value
350     : delete $flag->{$name};
351 root 1.1
352     } elsif ($cmd eq "setstate") {
353     my ($name, $value) = split /\s+/, $args, 2;
354 root 1.19 defined $value ? $state->{$name} = $value
355     : delete $state->{$name};
356 root 1.1
357     } elsif ($cmd eq "ifflag") {
358     my ($name, $value) = split /\s+/, $args, 2;
359 root 1.17 defined $value ? $flag->{$name} eq $value
360     : $flag->{$name}
361 root 1.1 or next topic;
362    
363     } elsif ($cmd eq "ifstate") {
364     my ($name, $value) = split /\s+/, $args, 2;
365 root 1.20 defined $value ? $state->{$name} eq $value
366     : $state->{$name}
367 root 1.1 or next topic;
368    
369     } elsif ($cmd eq "trigger") {
370 elmex 1.2 my ($con, $state) = split /\s+/, $args, 2;
371    
372     if (defined $state) {
373 root 1.14 $self->{npc}->map->trigger ($con, $state, $self->{npc}, $self->{ob});
374 elmex 1.2 } else {
375 root 1.14 my $rvalue = \$self->{npc}{dialog_trigger}{$con+0};
376 root 1.13 $self->{npc}->map->trigger ($con, $$rvalue = !$$rvalue, $self->{npc}, $self->{ob});
377 elmex 1.2 }
378 root 1.1
379     } elsif ($cmd eq "addtopic") {
380     push @kw, split /\|/, $args;
381     $self->{add_topic}->(split /\s*\|\s*/, $args) if $self->{add_topic};
382    
383     } elsif ($cmd eq "deltopic") {
384     # not yet implemented, do it out-of-band
385     $self->{del_topic}->(split /\s*\|\s*/, $args) if $self->{del_topic};
386    
387     } else {
388     warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->msg . ")";
389     }
390     }
391    
392     delete $self->{npc}{$self->{ob}->name}{dialog_state} unless %$state;
393     delete $self->{ob}{dialog_flag} unless %$flag;
394    
395     # ignores flags and npc from replies
396     $reply = join "\n", (map $_->[1], @replies), $reply;
397    
398     # now mark up all matching keywords
399     for my $match (@{ $self->{match} }) {
400     for (sort { (length $b) <=> (length $a) } split /\|/, $match->[0]) {
401     if ($reply =~ /\b\Q$_\E\b/i) {
402     push @kw, $_;
403     last;
404     }
405     }
406     }
407    
408 root 1.13 $self->{npc}->use_trigger ($self->{ob})
409     if $self->{npc}->type == cf::MAGIC_EAR;
410 root 1.12
411 root 1.1 return wantarray ? ($reply, @kw) : $reply;
412     }
413     }
414     }
415    
416     ()
417     }
418    
419     1