… | |
… | |
84 | |
84 | |
85 | =item $msg - The actual message as passed to this method. |
85 | =item $msg - The actual message as passed to this method. |
86 | |
86 | |
87 | =item $match - An arrayref with previous results from C<@parse>. |
87 | =item $match - An arrayref with previous results from C<@parse>. |
88 | |
88 | |
|
|
89 | =item $state - A hashref that stores state variables associated |
|
|
90 | with the NPC and the player, that is, it's values relate to the the |
|
|
91 | specific player-NPC interaction and other players will see a different |
|
|
92 | state. Useful to react to players in a stateful way. See C<@setstate> and |
|
|
93 | C<@ifstate>. |
|
|
94 | |
|
|
95 | =item $flag - A hashref that stores flags associated with the player and |
|
|
96 | can be seen by all NPCs (so better name your flags uniquely). This is |
|
|
97 | useful for storing e.g. quest information. See C<@setflag> and C<@ifflag>. |
|
|
98 | |
89 | =back |
99 | =back |
90 | |
100 | |
91 | The environment is that standard "map scripting environment", which is |
101 | The environment is that standard "map scripting environment", which is |
92 | limited in the type of constructs allowed (no loops, for example). |
102 | limited in the type of constructs allowed (no loops, for example). |
93 | |
103 | |
94 | =item @eval perl |
104 | =item @eval perl |
95 | |
105 | |
96 | Like C<@cond>, but proceed regardless of the outcome. |
106 | Like C<@cond>, but proceed regardless of the outcome. |
97 | |
107 | |
|
|
108 | =item @msg perl |
|
|
109 | |
|
|
110 | Like C<@cond>, but the return value will be stringified and prepended to |
|
|
111 | the message. |
|
|
112 | |
|
|
113 | =item @setstate state value |
|
|
114 | |
|
|
115 | Sets the named state C<state> to the given C<value>. State values are |
|
|
116 | associated with a specific player-NPC pair, so each NPC has its own state |
|
|
117 | with respect to a particular player, which makes them useful to store |
|
|
118 | information about previous questions and possibly answers. State values |
|
|
119 | get reset whenever the NPC gets reset. |
|
|
120 | |
|
|
121 | See C<@ifstate> for an example. |
|
|
122 | |
|
|
123 | =item @ifstate state value |
|
|
124 | |
|
|
125 | Requires that the named C<state> has the given C<value>, otherwise this |
|
|
126 | topic is skipped. For more complex comparisons, see C<@cond> with |
|
|
127 | C<$state>. Example: |
|
|
128 | |
|
|
129 | @match quest |
|
|
130 | @setstate question quest |
|
|
131 | Do you really want to help find the magic amulet of Beeblebrox? |
|
|
132 | @match yes |
|
|
133 | @ifstate question quest |
|
|
134 | Then fetch it, stupid! |
|
|
135 | |
|
|
136 | =item @setflag flag value |
|
|
137 | |
|
|
138 | Sets the named flag C<flag> to the given C<value>. Flag values are |
|
|
139 | associated with a specific player and can be seen by all NPCs. with |
|
|
140 | respect to a particular player, which makes them suitable to store quest |
|
|
141 | markers and other information (e.g. reputation/alignment). Flags are |
|
|
142 | persistent over the lifetime of a player, so be careful :) |
|
|
143 | |
|
|
144 | See C<@ifflag> for an example. |
|
|
145 | |
|
|
146 | =item @ifflag flag value |
|
|
147 | |
|
|
148 | Requires that the named C<flag> has the given C<value>, otherwise this |
|
|
149 | topic is skipped. For more complex comparisons, see C<@cond> with |
|
|
150 | C<$flag>. Example: |
|
|
151 | |
|
|
152 | @match I want to do the quest! |
|
|
153 | @setflag kings_quest 1 |
|
|
154 | Then seek out Bumblebee in Navar, he will tell you... |
|
|
155 | @match I did the quest |
|
|
156 | @ifflag kings_quest 1 |
|
|
157 | Really, which quets? |
|
|
158 | |
|
|
159 | And Bumblebee might have: |
|
|
160 | |
|
|
161 | @match hi |
|
|
162 | @ifflag kings_quest |
|
|
163 | Hi, I was told you want to do the kings quest? |
|
|
164 | |
98 | =item @trigger connected-id |
165 | =item @trigger connected-id |
99 | |
166 | |
100 | Trigger all objects with the given connected-id. The trigger is stateful |
167 | Trigger all objects with the given connected-id. The trigger is stateful |
101 | and retains state per connected-id. |
168 | and retains state per connected-id. |
102 | |
169 | |
… | |
… | |
112 | sub tell { |
179 | sub tell { |
113 | my ($self, $msg) = @_; |
180 | my ($self, $msg) = @_; |
114 | |
181 | |
115 | my $lcmsg = lc $msg; |
182 | my $lcmsg = lc $msg; |
116 | |
183 | |
117 | match: |
184 | topic: |
118 | for my $match (@{ $self->{match} }) { |
185 | for my $match (@{ $self->{match} }) { |
119 | for (split /\|/, $match->[0]) { |
186 | for (split /\|/, $match->[0]) { |
120 | if ($_ eq "*" || $lcmsg eq lc) { |
187 | if ($_ eq "*" || $lcmsg eq lc) { |
121 | my $reply = $match->[1]; |
188 | my $reply = $match->[1]; |
122 | my @kw; |
189 | my @kw; |
123 | |
190 | |
124 | my @replies; |
191 | my @replies; |
125 | my @match; # @match/@parse command results |
192 | my @match; # @match/@parse command results |
|
|
193 | |
|
|
194 | my $state = $self->{npc}{$self->{ob}->name}{dialog_state} ||= {}; |
|
|
195 | my $flag = $self->{ob}{dialog_flag} ||= {}; |
|
|
196 | |
|
|
197 | my %vars = ( |
|
|
198 | who => $self->{ob}, |
|
|
199 | npc => $self->{npc}, |
|
|
200 | state => $state, |
|
|
201 | flag => $flag, |
|
|
202 | msg => $msg, |
|
|
203 | match => \@match, |
|
|
204 | ); |
|
|
205 | |
126 | local $self->{ob}{record_replies} = \@replies; |
206 | local $self->{ob}{record_replies} = \@replies; |
127 | |
207 | |
128 | # now execute @-commands (which can result in a no-match) |
208 | # now execute @-commands (which can result in a no-match) |
129 | while ($reply =~ s/^\@(\w+)\s*([^\n]*)\n?//) { |
209 | while ($reply =~ s/^\@(\w+)\s*([^\n]*)\n?//) { |
130 | my ($cmd, $args) = ($1, $2); |
210 | my ($cmd, $args) = ($1, $2); |
131 | |
211 | |
132 | if ($cmd eq "parse" || $cmd eq "match") { # match is future rename |
212 | if ($cmd eq "parse" || $cmd eq "match") { # match is future rename |
133 | no re 'eval'; # default, but make sure |
213 | no re 'eval'; # default, but make sure |
134 | @match = $msg =~ /$args/i |
214 | @match = $msg =~ /$args/i |
135 | or next match; |
215 | or next topic; |
136 | |
216 | |
137 | } elsif ($cmd eq "cond") { |
217 | } elsif ($cmd eq "cond") { |
138 | cf::safe_eval $args, who => $self->{ob}, npc => $self->{npc}, msg => $msg, match => \@match |
218 | cf::safe_eval $args, %vars |
139 | or next match; |
219 | or next topic; |
140 | |
220 | |
141 | } elsif ($cmd eq "eval") { |
221 | } elsif ($cmd eq "eval") { |
142 | cf::safe_eval $args, who => $self->{ob}, npc => $self->{npc}, msg => $msg, match => \@match; |
222 | cf::safe_eval $args, %vars; |
143 | warn "\@eval evaluation error: $@\n" if $@; |
223 | warn "\@eval evaluation error: $@\n" if $@; |
|
|
224 | |
|
|
225 | } elsif ($cmd eq "msg") { |
|
|
226 | push @replies, [$self->{npc}, (scalar cf::safe_eval $args, %vars)]; |
|
|
227 | |
|
|
228 | } elsif ($cmd eq "setflag") { |
|
|
229 | my ($name, $value) = split /\s+/, $args, 2; |
|
|
230 | $value ? $flag->{$name} = $value |
|
|
231 | : delete $flag->{$name}; |
|
|
232 | |
|
|
233 | } elsif ($cmd eq "setstate") { |
|
|
234 | my ($name, $value) = split /\s+/, $args, 2; |
|
|
235 | $value ? $state->{$name} = $value |
|
|
236 | : delete $state->{$name}; |
|
|
237 | |
|
|
238 | } elsif ($cmd eq "ifflag") { |
|
|
239 | my ($name, $value) = split /\s+/, $args, 2; |
|
|
240 | $flag->{$name} eq $value |
|
|
241 | or next topic; |
|
|
242 | |
|
|
243 | } elsif ($cmd eq "ifstate") { |
|
|
244 | my ($name, $value) = split /\s+/, $args, 2; |
|
|
245 | $state->{$name} eq $value |
|
|
246 | or next topic; |
144 | |
247 | |
145 | } elsif ($cmd eq "trigger") { |
248 | } elsif ($cmd eq "trigger") { |
146 | my $rvalue = \$self->{npc}{dialog_trigger}{$args*1}; |
249 | my $rvalue = \$self->{npc}{dialog_trigger}{$args*1}; |
147 | |
250 | |
148 | my $trigger = cf::object::new "magic_ear"; |
251 | my $trigger = cf::object::new "magic_ear"; |
… | |
… | |
162 | |
265 | |
163 | $$rvalue = !$$rvalue; |
266 | $$rvalue = !$$rvalue; |
164 | |
267 | |
165 | } elsif ($cmd eq "addtopic") { |
268 | } elsif ($cmd eq "addtopic") { |
166 | push @kw, split /\|/, $args; |
269 | push @kw, split /\|/, $args; |
|
|
270 | $self->{add_topic}->(split /\s*\|\s*/, $args) if $self->{add_topic}; |
|
|
271 | |
|
|
272 | } elsif ($cmd eq "deltopic") { |
|
|
273 | # not yet implemented, do it out-of-band |
|
|
274 | $self->{del_topic}->(split /\s*\|\s*/, $args) if $self->{del_topic}; |
167 | |
275 | |
168 | } else { |
276 | } else { |
169 | warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->get_message . ")"; |
277 | warn "unknown dialogue command <$cmd,$args> used (from " . $self->{npc}->get_message . ")"; |
170 | } |
278 | } |
171 | } |
279 | } |
|
|
280 | |
|
|
281 | delete $self->{npc}{$self->{ob}->name}{dialog_state} unless %$state; |
|
|
282 | delete $self->{ob}{dialog_flag} unless %$flag; |
172 | |
283 | |
173 | # combine lines into paragraphs |
284 | # combine lines into paragraphs |
174 | $reply =~ s/(?<=\S)\n(?=\w)/ /g; |
285 | $reply =~ s/(?<=\S)\n(?=\w)/ /g; |
175 | $reply =~ s/\n\n/\n/g; |
286 | $reply =~ s/\n\n/\n/g; |
176 | |
287 | |