root/src/map/npc_chat.c @ 16

Revision 1, 11.9 kB (checked in by jinshiro, 17 years ago)
Line 
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 */
73struct 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 */
82struct 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 */
98struct 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 */
109void 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 */
120static 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 */
161static 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 */
194static 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 */
230static 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 */
281static 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 */
310void 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 */
329void 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 */
348int 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
406int 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
417int 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
429int 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
439int 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
449int 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
Note: See TracBrowser for help on using the browser.