ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/NPC_Dialogue.pm
(Generate patch)

Comparing deliantra/server/ext/NPC_Dialogue.pm (file contents):
Revision 1.1 by root, Fri Dec 15 19:29:18 2006 UTC vs.
Revision 1.19 by root, Fri Feb 5 01:26:35 2010 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines