ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/NPC_Dialogue.pm
Revision: 1.16
Committed: Mon Oct 26 02:48:02 2009 UTC (14 years, 7 months ago) by root
Branch: MAIN
Changes since 1.15: +10 -2 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 NPC_Dialogue
4
5 =head1 DESCRIPTION
6
7 NPC dialogue support module.
8
9 =over 4
10
11 =cut
12
13 package NPC_Dialogue;
14
15 use strict;
16
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 $arg{ob} = $arg{pl}->ob;
28
29 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 =item $msg - The actual message as passed to this method.
88
89 =item $match - An arrayref with previous results from C<@parse>.
90
91 =item $state - A hashref that stores state variables associated
92 with the NPC and the player, that is, it's values relate to the the
93 specific player-NPC interaction and other players will see a different
94 state. Useful to react to players in a stateful way. See C<@setstate> and
95 C<@ifstate>.
96
97 =item $flag - A hashref that stores flags associated with the player and
98 can be seen by all NPCs (so better name your flags uniquely). This is
99 useful for storing e.g. quest information. See C<@setflag> and C<@ifflag>.
100
101 =item @find - see @find, below.
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 Here is a example:
109
110 =over 4
111
112 =item B<matching for an item name>
113
114 @match hi
115 @cond grep $_->name =~ /royalty/, $who->inv
116 You got royalties there! Wanna have!
117
118 You may want to change the C<name> method there to something like C<title>,
119 C<slaying> or any other method that is allowed to be called on a
120 C<cf::object> here.
121
122 =item B<matching for an item name and removing the matched item>
123
124 @match found earhorn
125 @cond grep $_->slaying =~ /Gramp's walking stick/, $who->inv
126 @eval my @g = grep { $_->slaying =~ /Gramp's walking stick/ } $who->inv; $g[0]->decrease;
127 Thanks for the earhorn!
128
129 This example is a bit more complex. The C<@eval> statement will search
130 the players inventory for the same term as the C<@cond> and then
131 decreases the number of objects used there.
132
133 (See also the map: C<scorn/houses/cornerbrook.map> for an example how this is
134 used in the real world :-)
135
136 =back
137
138 =item @eval perl
139
140 Like C<@cond>, but proceed regardless of the outcome.
141
142 =item @msg perl
143
144 Like C<@cond>, but the return value will be stringified and prepended to
145 the reply message.
146
147 =item @check match expression
148
149 Executes a match expression (see
150 http://pod.tst.eu/http://cvs.schmorp.de/deliantra/server/lib/cf/match.pm)
151 to see if it matches.
152
153 C<self> is the npc object, C<object>, C<source> and C<originator> are the
154 player communicating with the NPC.
155
156 If the check fails, the match is skipped.
157
158 =item @find match expression
159
160 Like C<@check> in that it executes a match expression, but instead of
161 failing, it gathers all objects matched into the C<@find> array variable.
162
163 When you want to skip the match when no objects have been found, combine
164 C<@find> with C<@cond>:
165
166 @match see my spellbook
167 @find type=SPELLBOOK in inv
168 @cond @find
169 It looks dirty.
170 @match see my spellbook
171 I can't see any, where do you have it?
172
173 =item @setstate state value
174
175 Sets the named state C<state> to the given C<value>. State values are
176 associated with a specific player-NPC pair, so each NPC has its own state
177 with respect to a particular player, which makes them useful to store
178 information about previous questions and possibly answers. State values
179 get reset whenever the NPC gets reset.
180
181 See C<@ifstate> for an example.
182
183 =item @ifstate state value
184
185 Requires that the named C<state> has the given C<value>, otherwise this
186 topic is skipped. For more complex comparisons, see C<@cond> with
187 C<$state>. Example:
188
189 @match quest
190 @setstate question quest
191 Do you really want to help find the magic amulet of Beeblebrox?
192 @match yes
193 @ifstate question quest
194 Then fetch it, stupid!
195
196 =item @setflag flag value
197
198 Sets the named flag C<flag> to the given C<value>. Flag values are
199 associated with a specific player and can be seen by all NPCs. with
200 respect to a particular player, which makes them suitable to store quest
201 markers and other information (e.g. reputation/alignment). Flags are
202 persistent over the lifetime of a player, so be careful :)
203
204 Perversely enough, using C<@setfflag> without a C<value> clears the flag
205 as if it was never set, so always provide a flag value (e.g. C<1>) when
206 you want to set the flag.
207
208 See C<@ifflag> for an example.
209
210 =item @ifflag flag value
211
212 Requires that the named C<flag> has the given C<value>, otherwise this
213 topic is skipped. For more complex comparisons, see C<@cond> with
214 C<$flag>.
215
216 If no C<value> is given, then the ifflag succeeds when the flag is true.
217
218 Example:
219
220 @match I want to do the quest!
221 @setflag kings_quest 1
222 Then seek out Bumblebee in Navar, he will tell you...
223 @match I did the quest
224 @ifflag kings_quest 1
225 Really, which quets?
226
227 And Bumblebee might have:
228
229 @match hi
230 @ifflag kings_quest
231 Hi, I was told you want to do the kings quest?
232
233 =item @trigger connected-id [state]
234
235 Trigger all objects with the given connected-id.
236
237 When the state argument is omitted the trigger is stateful and retains an
238 internal state per connected-id. There is a limitation to the use of this: The
239 state won't be changed when the connection is triggered by other triggers. So
240 be careful when triggering the connection from other objects.
241
242 When a state argument is given it should be a positive integer. Any value
243 C<!= 0> will 'push' the connection (in general, you should specify C<1>
244 for this) and C<0> will 'release' the connection. This is useful for
245 example when you want to let an NPC control a door.
246
247 Trigger all objects with the given connected-id by 'releasing' the connection.
248
249 =item @playersound face-name
250
251 Plays the given sound face (either an alias or sound file path) so that
252 only the player talking to the npc can hear it.
253
254 =item @npcsound face-name
255
256 Plays the given sound face (either an alias or sound file path) as if
257 the npc had made that sound, i.e. it will be located at the npc and all
258 players near enough can hear it.
259
260 =item @addtopic topic
261
262 Adds the given topic names (separated by C<|>) to the list of topics
263 returned.
264
265 =back
266
267 =cut
268
269 sub tell {
270 my ($self, $msg) = @_;
271
272 my $lcmsg = lc $msg;
273
274 topic:
275 for my $match (@{ $self->{match} }) {
276 for (split /\|/, $match->[0]) {
277 if ($_ eq "*" || $lcmsg =~ /\b\Q$_\E\b/i) {
278 my $reply = $match->[1];
279 my @kw;
280
281 my @replies;
282 my @match; # @match/@parse command results
283
284 my $state = $self->{npc}{$self->{ob}->name}{dialog_state} ||= {};
285 my $flag = $self->{ob}{dialog_flag} ||= {};
286
287 my @find;
288
289 my %vars = (
290 who => $self->{ob},
291 npc => $self->{npc},
292 state => $state,
293 flag => $flag,
294 msg => $msg,
295 match => \@match,
296 );
297
298 local $self->{ob}{record_replies} = \@replies;
299
300 # now execute @-commands (which can result in a no-match)
301 while ($reply =~ s/^\@(\w+)\s*([^\n]*)\n?//) {
302 my ($cmd, $args) = ($1, $2);
303
304 if ($cmd eq "parse" || $cmd eq "match") { # match is future rename
305 no re 'eval'; # default, but make sure
306 @match = $msg =~ /$args/i
307 or next topic;
308
309 } elsif ($cmd eq "comment") {
310 # nop
311
312 } elsif ($cmd eq "playersound") {
313 $self->{ob}->contr->play_sound (cf::sound::find $args);
314
315 } elsif ($cmd eq "npcsound") {
316 $self->{npc}->play_sound (cf::sound::find $args);
317
318 } elsif ($cmd eq "cond") {
319 cf::safe_eval $args, %vars
320 or next topic;
321
322 } elsif ($cmd eq "eval") {
323 cf::safe_eval $args, %vars;
324 warn "\@eval evaluation error: $@\n" if $@;
325
326 } elsif ($cmd eq "check") {
327 eval {
328 cf::match::match $args, $self->{ob}, $self->{npc}, $self->{ob}
329 or next topic;
330 };
331 warn "\@check evaluation error: $@\n" if $@;
332
333 } elsif ($cmd eq "find") {
334 @find = eval {
335 cf::match::match $args, $self->{ob}, $self->{npc}, $self->{ob}
336 };
337 warn "\@find evaluation error: $@\n" if $@;
338
339 } elsif ($cmd eq "msg") {
340 push @replies, [$self->{npc}, (scalar cf::safe_eval $args, %vars)];
341
342 } elsif ($cmd eq "setflag") {
343 my ($name, $value) = split /\s+/, $args, 2;
344 $value ? $flag->{$name} = $value
345 : delete $flag->{$name};
346
347 } elsif ($cmd eq "setstate") {
348 my ($name, $value) = split /\s+/, $args, 2;
349 $value ? $state->{$name} = $value
350 : delete $state->{$name};
351
352 } elsif ($cmd eq "ifflag") {
353 my ($name, $value) = split /\s+/, $args, 2;
354 $flag->{$name} eq $value
355 or next topic;
356
357 } elsif ($cmd eq "ifstate") {
358 my ($name, $value) = split /\s+/, $args, 2;
359 $state->{$name} eq $value
360 or next topic;
361
362 } elsif ($cmd eq "trigger") {
363 my ($con, $state) = split /\s+/, $args, 2;
364
365 if (defined $state) {
366 $self->{npc}->map->trigger ($con, $state, $self->{npc}, $self->{ob});
367 } else {
368 my $rvalue = \$self->{npc}{dialog_trigger}{$con+0};
369 $self->{npc}->map->trigger ($con, $$rvalue = !$$rvalue, $self->{npc}, $self->{ob});
370 }
371
372 } elsif ($cmd eq "addtopic") {
373 push @kw, split /\|/, $args;
374 $self->{add_topic}->(split /\s*\|\s*/, $args) if $self->{add_topic};
375
376 } elsif ($cmd eq "deltopic") {
377 # not yet implemented, do it out-of-band
378 $self->{del_topic}->(split /\s*\|\s*/, $args) if $self->{del_topic};
379
380 } else {
381 warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->msg . ")";
382 }
383 }
384
385 delete $self->{npc}{$self->{ob}->name}{dialog_state} unless %$state;
386 delete $self->{ob}{dialog_flag} unless %$flag;
387
388 # ignores flags and npc from replies
389 $reply = join "\n", (map $_->[1], @replies), $reply;
390
391 # now mark up all matching keywords
392 for my $match (@{ $self->{match} }) {
393 for (sort { (length $b) <=> (length $a) } split /\|/, $match->[0]) {
394 if ($reply =~ /\b\Q$_\E\b/i) {
395 push @kw, $_;
396 last;
397 }
398 }
399 }
400
401 $self->{npc}->use_trigger ($self->{ob})
402 if $self->{npc}->type == cf::MAGIC_EAR;
403
404 return wantarray ? ($reply, @kw) : $reply;
405 }
406 }
407 }
408
409 ()
410 }
411
412 1