1 | // Copyright (c) Athena Dev Teams - Licensed under GNU GPL |
---|
2 | // For more information, see LICENCE in the main folder |
---|
3 | |
---|
4 | #ifdef PCRE_SUPPORT |
---|
5 | |
---|
6 | #include "../common/timer.h" |
---|
7 | #include "../common/malloc.h" |
---|
8 | #include "../common/nullpo.h" |
---|
9 | #include "../common/showmsg.h" |
---|
10 | #include "../common/strlib.h" |
---|
11 | |
---|
12 | #include "mob.h" // struct mob_data |
---|
13 | #include "npc.h" // struct npc_data |
---|
14 | #include "pc.h" // struct map_session_data |
---|
15 | #include "script.h" // set_var() |
---|
16 | |
---|
17 | #include "pcre.h" |
---|
18 | |
---|
19 | #include <stdio.h> |
---|
20 | #include <stdlib.h> |
---|
21 | #include <string.h> |
---|
22 | #include <stdarg.h> |
---|
23 | |
---|
24 | |
---|
25 | /** |
---|
26 | * Written by MouseJstr in a vision... (2/21/2005) |
---|
27 | * |
---|
28 | * This allows you to make npc listen for spoken text (global |
---|
29 | * messages) and pattern match against that spoken text using perl |
---|
30 | * regular expressions. |
---|
31 | * |
---|
32 | * Please feel free to copy this code into your own personal ragnarok |
---|
33 | * servers or distributions but please leave my name. Also, please |
---|
34 | * wait until I've put it into the main eA branch which means I |
---|
35 | * believe it is ready for distribution. |
---|
36 | * |
---|
37 | * So, how do people use this? |
---|
38 | * |
---|
39 | * The first and most important function is defpattern |
---|
40 | * |
---|
41 | * defpattern 1, "[^:]+: (.*) loves (.*)", "label"; |
---|
42 | * |
---|
43 | * this defines a new pattern in set 1 using perl syntax |
---|
44 | * (http://www.troubleshooters.com/codecorn/littperl/perlreg.htm) |
---|
45 | * and tells it to jump to the supplied label when the pattern |
---|
46 | * is matched. |
---|
47 | * |
---|
48 | * each of the matched Groups will result in a variable being |
---|
49 | * set ($@p1$ through $@p9$ with $@p0$ being the entire string) |
---|
50 | * before the script gets executed. |
---|
51 | * |
---|
52 | * activatepset 1; |
---|
53 | * |
---|
54 | * This activates a set of patterns.. You can have many pattern |
---|
55 | * sets defined and many active all at once. This feature allows |
---|
56 | * you to set up "conversations" and ever changing expectations of |
---|
57 | * the pattern matcher |
---|
58 | * |
---|
59 | * deactivatepset 1; |
---|
60 | * |
---|
61 | * turns off a pattern set; |
---|
62 | * |
---|
63 | * deactivatepset -1; |
---|
64 | * |
---|
65 | * turns off ALL pattern sets; |
---|
66 | * |
---|
67 | * deletepset 1; |
---|
68 | * |
---|
69 | * deletes a pset |
---|
70 | */ |
---|
71 | |
---|
72 | /* Structure containing all info associated with a single pattern block */ |
---|
73 | struct pcrematch_entry { |
---|
74 | struct pcrematch_entry* next; |
---|
75 | char* pattern; |
---|
76 | pcre* pcre; |
---|
77 | pcre_extra* pcre_extra; |
---|
78 | char* label; |
---|
79 | }; |
---|
80 | |
---|
81 | /* A set of patterns that can be activated and deactived with a single command */ |
---|
82 | struct pcrematch_set { |
---|
83 | struct pcrematch_set* prev; |
---|
84 | struct pcrematch_set* next; |
---|
85 | struct pcrematch_entry* head; |
---|
86 | int setid; |
---|
87 | }; |
---|
88 | |
---|
89 | /* |
---|
90 | * Entire data structure hung off a NPC |
---|
91 | * |
---|
92 | * The reason I have done it this way (a void * in npc_data and then |
---|
93 | * this) was to reduce the number of patches that needed to be applied |
---|
94 | * to a ragnarok distribution to bring this code online. I |
---|
95 | * also wanted people to be able to grab this one file to get updates |
---|
96 | * without having to do a large number of changes. |
---|
97 | */ |
---|
98 | struct npc_parse { |
---|
99 | struct pcrematch_set* active; |
---|
100 | struct pcrematch_set* inactive; |
---|
101 | }; |
---|
102 | |
---|
103 | |
---|
104 | /** |
---|
105 | * delete everythign associated with a entry |
---|
106 | * |
---|
107 | * This does NOT do the list management |
---|
108 | */ |
---|
109 | void finalize_pcrematch_entry(struct pcrematch_entry* e) |
---|
110 | { |
---|
111 | pcre_free(e->pcre); |
---|
112 | pcre_free(e->pcre_extra); |
---|
113 | aFree(e->pattern); |
---|
114 | aFree(e->label); |
---|
115 | } |
---|
116 | |
---|
117 | /** |
---|
118 | * Lookup (and possibly create) a new set of patterns by the set id |
---|
119 | */ |
---|
120 | static struct pcrematch_set* lookup_pcreset(struct npc_data* nd, int setid) |
---|
121 | { |
---|
122 | struct pcrematch_set *pcreset; |
---|
123 | struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; |
---|
124 | if (npcParse == NULL) |
---|
125 | nd->chatdb = npcParse = (struct npc_parse *) aCalloc(sizeof(struct npc_parse), 1); |
---|
126 | |
---|
127 | pcreset = npcParse->active; |
---|
128 | |
---|
129 | while (pcreset != NULL) { |
---|
130 | if (pcreset->setid == setid) |
---|
131 | break; |
---|
132 | pcreset = pcreset->next; |
---|
133 | } |
---|
134 | if (pcreset == NULL) |
---|
135 | pcreset = npcParse->inactive; |
---|
136 | |
---|
137 | while (pcreset != NULL) { |
---|
138 | if (pcreset->setid == setid) |
---|
139 | break; |
---|
140 | pcreset = pcreset->next; |
---|
141 | } |
---|
142 | |
---|
143 | if (pcreset == NULL) { |
---|
144 | pcreset = (struct pcrematch_set *) aCalloc(sizeof(struct pcrematch_set), 1); |
---|
145 | pcreset->next = npcParse->inactive; |
---|
146 | if (pcreset->next != NULL) |
---|
147 | pcreset->next->prev = pcreset; |
---|
148 | pcreset->prev = 0; |
---|
149 | npcParse->inactive = pcreset; |
---|
150 | pcreset->setid = setid; |
---|
151 | } |
---|
152 | |
---|
153 | return pcreset; |
---|
154 | } |
---|
155 | |
---|
156 | /** |
---|
157 | * activate a set of patterns. |
---|
158 | * |
---|
159 | * if the setid does not exist, this will silently return |
---|
160 | */ |
---|
161 | static void activate_pcreset(struct npc_data* nd, int setid) |
---|
162 | { |
---|
163 | struct pcrematch_set *pcreset; |
---|
164 | struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; |
---|
165 | if (npcParse == NULL) |
---|
166 | return; // Nothing to activate... |
---|
167 | pcreset = npcParse->inactive; |
---|
168 | while (pcreset != NULL) { |
---|
169 | if (pcreset->setid == setid) |
---|
170 | break; |
---|
171 | pcreset = pcreset->next; |
---|
172 | } |
---|
173 | if (pcreset == NULL) |
---|
174 | return; // not in inactive list |
---|
175 | if (pcreset->next != NULL) |
---|
176 | pcreset->next->prev = pcreset->prev; |
---|
177 | if (pcreset->prev != NULL) |
---|
178 | pcreset->prev->next = pcreset->next; |
---|
179 | else |
---|
180 | npcParse->inactive = pcreset->next; |
---|
181 | |
---|
182 | pcreset->prev = NULL; |
---|
183 | pcreset->next = npcParse->active; |
---|
184 | if (pcreset->next != NULL) |
---|
185 | pcreset->next->prev = pcreset; |
---|
186 | npcParse->active = pcreset; |
---|
187 | } |
---|
188 | |
---|
189 | /** |
---|
190 | * deactivate a set of patterns. |
---|
191 | * |
---|
192 | * if the setid does not exist, this will silently return |
---|
193 | */ |
---|
194 | static void deactivate_pcreset(struct npc_data* nd, int setid) |
---|
195 | { |
---|
196 | struct pcrematch_set *pcreset; |
---|
197 | struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; |
---|
198 | if (npcParse == NULL) |
---|
199 | return; // Nothing to deactivate... |
---|
200 | if (setid == -1) { |
---|
201 | while(npcParse->active != NULL) |
---|
202 | deactivate_pcreset(nd, npcParse->active->setid); |
---|
203 | return; |
---|
204 | } |
---|
205 | pcreset = npcParse->active; |
---|
206 | while (pcreset != NULL) { |
---|
207 | if (pcreset->setid == setid) |
---|
208 | break; |
---|
209 | pcreset = pcreset->next; |
---|
210 | } |
---|
211 | if (pcreset == NULL) |
---|
212 | return; // not in active list |
---|
213 | if (pcreset->next != NULL) |
---|
214 | pcreset->next->prev = pcreset->prev; |
---|
215 | if (pcreset->prev != NULL) |
---|
216 | pcreset->prev->next = pcreset->next; |
---|
217 | else |
---|
218 | npcParse->active = pcreset->next; |
---|
219 | |
---|
220 | pcreset->prev = NULL; |
---|
221 | pcreset->next = npcParse->inactive; |
---|
222 | if (pcreset->next != NULL) |
---|
223 | pcreset->next->prev = pcreset; |
---|
224 | npcParse->inactive = pcreset; |
---|
225 | } |
---|
226 | |
---|
227 | /** |
---|
228 | * delete a set of patterns. |
---|
229 | */ |
---|
230 | static void delete_pcreset(struct npc_data* nd, int setid) |
---|
231 | { |
---|
232 | int active = 1; |
---|
233 | struct pcrematch_set *pcreset; |
---|
234 | struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; |
---|
235 | if (npcParse == NULL) |
---|
236 | return; // Nothing to deactivate... |
---|
237 | pcreset = npcParse->active; |
---|
238 | while (pcreset != NULL) { |
---|
239 | if (pcreset->setid == setid) |
---|
240 | break; |
---|
241 | pcreset = pcreset->next; |
---|
242 | } |
---|
243 | if (pcreset == NULL) { |
---|
244 | active = 0; |
---|
245 | pcreset = npcParse->inactive; |
---|
246 | while (pcreset != NULL) { |
---|
247 | if (pcreset->setid == setid) |
---|
248 | break; |
---|
249 | pcreset = pcreset->next; |
---|
250 | } |
---|
251 | } |
---|
252 | if (pcreset == NULL) |
---|
253 | return; |
---|
254 | |
---|
255 | if (pcreset->next != NULL) |
---|
256 | pcreset->next->prev = pcreset->prev; |
---|
257 | if (pcreset->prev != NULL) |
---|
258 | pcreset->prev->next = pcreset->next; |
---|
259 | |
---|
260 | if(active) |
---|
261 | npcParse->active = pcreset->next; |
---|
262 | else |
---|
263 | npcParse->inactive = pcreset->next; |
---|
264 | |
---|
265 | pcreset->prev = NULL; |
---|
266 | pcreset->next = NULL; |
---|
267 | |
---|
268 | while (pcreset->head) { |
---|
269 | struct pcrematch_entry* n = pcreset->head->next; |
---|
270 | finalize_pcrematch_entry(pcreset->head); |
---|
271 | aFree(pcreset->head); // Cleanin' the last ones.. [Lance] |
---|
272 | pcreset->head = n; |
---|
273 | } |
---|
274 | |
---|
275 | aFree(pcreset); |
---|
276 | } |
---|
277 | |
---|
278 | /** |
---|
279 | * create a new pattern entry |
---|
280 | */ |
---|
281 | static struct pcrematch_entry* create_pcrematch_entry(struct pcrematch_set* set) |
---|
282 | { |
---|
283 | struct pcrematch_entry * e = (struct pcrematch_entry *) aCalloc(sizeof(struct pcrematch_entry), 1); |
---|
284 | struct pcrematch_entry * last = set->head; |
---|
285 | |
---|
286 | // Normally we would have just stuck it at the end of the list but |
---|
287 | // this doesn't sink up with peoples usage pattern. They wanted |
---|
288 | // the items defined first to have a higher priority then the |
---|
289 | // items defined later. as a result, we have to do some work up front. |
---|
290 | |
---|
291 | /* if we are the first pattern, stick us at the end */ |
---|
292 | if (last == NULL) { |
---|
293 | set->head = e; |
---|
294 | return e; |
---|
295 | } |
---|
296 | |
---|
297 | /* Look for the last entry */ |
---|
298 | while (last->next != NULL) |
---|
299 | last = last->next; |
---|
300 | |
---|
301 | last->next = e; |
---|
302 | e->next = NULL; |
---|
303 | |
---|
304 | return e; |
---|
305 | } |
---|
306 | |
---|
307 | /** |
---|
308 | * define/compile a new pattern |
---|
309 | */ |
---|
310 | void npc_chat_def_pattern(struct npc_data* nd, int setid, const char* pattern, const char* label) |
---|
311 | { |
---|
312 | const char *err; |
---|
313 | int erroff; |
---|
314 | |
---|
315 | struct pcrematch_set * s = lookup_pcreset(nd, setid); |
---|
316 | struct pcrematch_entry *e = create_pcrematch_entry(s); |
---|
317 | e->pattern = aStrdup(pattern); |
---|
318 | e->label = aStrdup(label); |
---|
319 | e->pcre = pcre_compile(pattern, PCRE_CASELESS, &err, &erroff, NULL); |
---|
320 | e->pcre_extra = pcre_study(e->pcre, 0, &err); |
---|
321 | } |
---|
322 | |
---|
323 | /** |
---|
324 | * Delete everything associated with a NPC concerning the pattern |
---|
325 | * matching code |
---|
326 | * |
---|
327 | * this could be more efficent but.. how often do you do this? |
---|
328 | */ |
---|
329 | void npc_chat_finalize(struct npc_data* nd) |
---|
330 | { |
---|
331 | struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; |
---|
332 | if (npcParse == NULL) |
---|
333 | return; |
---|
334 | |
---|
335 | while(npcParse->active) |
---|
336 | delete_pcreset(nd, npcParse->active->setid); |
---|
337 | |
---|
338 | while(npcParse->inactive) |
---|
339 | delete_pcreset(nd, npcParse->inactive->setid); |
---|
340 | |
---|
341 | // Additional cleaning up [Lance] |
---|
342 | aFree(npcParse); |
---|
343 | } |
---|
344 | |
---|
345 | /** |
---|
346 | * Handler called whenever a global message is spoken in a NPC's area |
---|
347 | */ |
---|
348 | int npc_chat_sub(struct block_list* bl, va_list ap) |
---|
349 | { |
---|
350 | struct npc_data* nd = (struct npc_data *) bl; |
---|
351 | struct npc_parse* npcParse = (struct npc_parse *) nd->chatdb; |
---|
352 | char* msg; |
---|
353 | int len, i; |
---|
354 | struct map_session_data* sd; |
---|
355 | struct npc_label_list* lst; |
---|
356 | struct pcrematch_set* pcreset; |
---|
357 | struct pcrematch_entry* e; |
---|
358 | |
---|
359 | // Not interested in anything you might have to say... |
---|
360 | if (npcParse == NULL || npcParse->active == NULL) |
---|
361 | return 0; |
---|
362 | |
---|
363 | msg = va_arg(ap,char*); |
---|
364 | len = va_arg(ap,int); |
---|
365 | sd = va_arg(ap,struct map_session_data *); |
---|
366 | |
---|
367 | // iterate across all active sets |
---|
368 | for (pcreset = npcParse->active; pcreset != NULL; pcreset = pcreset->next) |
---|
369 | { |
---|
370 | // interate across all patterns in that set |
---|
371 | for (e = pcreset->head; e != NULL; e = e->next) |
---|
372 | { |
---|
373 | int offsets[2*10 + 10]; // 1/3 reserved for temp space requred by pcre_exec |
---|
374 | |
---|
375 | // perform pattern match |
---|
376 | int r = pcre_exec(e->pcre, e->pcre_extra, msg, len, 0, 0, offsets, ARRAYLENGTH(offsets)); |
---|
377 | if (r > 0) |
---|
378 | { |
---|
379 | // save out the matched strings |
---|
380 | for (i = 0; i < r; i++) |
---|
381 | { |
---|
382 | char var[6], val[255]; |
---|
383 | snprintf(var, sizeof(var), "$@p%i$", i); |
---|
384 | pcre_copy_substring(msg, offsets, r, i, val, sizeof(val)); |
---|
385 | set_var(sd, var, val); |
---|
386 | } |
---|
387 | |
---|
388 | // find the target label.. this sucks.. |
---|
389 | lst = nd->u.scr.label_list; |
---|
390 | ARR_FIND(0, nd->u.scr.label_list_num, i, strncmp(lst[i].name, e->label, sizeof(lst[i].name)) == 0); |
---|
391 | if (i == nd->u.scr.label_list_num) { |
---|
392 | ShowWarning("Unable to find label: %s", e->label); |
---|
393 | return 0; |
---|
394 | } |
---|
395 | |
---|
396 | // run the npc script |
---|
397 | run_script(nd->u.scr.script,lst[i].pos,sd->bl.id,nd->bl.id); |
---|
398 | return 0; |
---|
399 | } |
---|
400 | } |
---|
401 | } |
---|
402 | |
---|
403 | return 0; |
---|
404 | } |
---|
405 | |
---|
406 | int mob_chat_sub(struct block_list* bl, va_list ap) |
---|
407 | { |
---|
408 | struct mob_data *md = (struct mob_data *)bl; |
---|
409 | if(md->nd) |
---|
410 | npc_chat_sub(&md->nd->bl, ap); |
---|
411 | |
---|
412 | return 0; |
---|
413 | } |
---|
414 | |
---|
415 | // Various script builtins used to support these functions |
---|
416 | |
---|
417 | int buildin_defpattern(struct script_state* st) |
---|
418 | { |
---|
419 | int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); |
---|
420 | const char* pattern = conv_str(st,& (st->stack->stack_data[st->start+3])); |
---|
421 | const char* label = conv_str(st,& (st->stack->stack_data[st->start+4])); |
---|
422 | struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); |
---|
423 | |
---|
424 | npc_chat_def_pattern(nd, setid, pattern, label); |
---|
425 | |
---|
426 | return 0; |
---|
427 | } |
---|
428 | |
---|
429 | int buildin_activatepset(struct script_state* st) |
---|
430 | { |
---|
431 | int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); |
---|
432 | struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); |
---|
433 | |
---|
434 | activate_pcreset(nd, setid); |
---|
435 | |
---|
436 | return 0; |
---|
437 | } |
---|
438 | |
---|
439 | int buildin_deactivatepset(struct script_state* st) |
---|
440 | { |
---|
441 | int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); |
---|
442 | struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); |
---|
443 | |
---|
444 | deactivate_pcreset(nd, setid); |
---|
445 | |
---|
446 | return 0; |
---|
447 | } |
---|
448 | |
---|
449 | int buildin_deletepset(struct script_state* st) |
---|
450 | { |
---|
451 | int setid = conv_num(st,& (st->stack->stack_data[st->start+2])); |
---|
452 | struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); |
---|
453 | |
---|
454 | delete_pcreset(nd, setid); |
---|
455 | |
---|
456 | return 0; |
---|
457 | } |
---|
458 | |
---|
459 | #endif //PCRE_SUPPORT |
---|