// Copyright (c) Athena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #ifdef PCRE_SUPPORT #include #include #include #include #include #include "../common/timer.h" #include "../common/malloc.h" #include "../common/version.h" #include "../common/nullpo.h" #include "../common/showmsg.h" #include "map.h" #include "status.h" #include "npc.h" #include "chat.h" #include "script.h" #include "battle.h" #include "pcre.h" /** * Written by MouseJstr in a vision... (2/21/2005) * * This allows you to make npc listen for spoken text (global * messages) and pattern match against that spoken text using perl * regular expressions. * * Please feel free to copy this code into your own personal ragnarok * servers or distributions but please leave my name. Also, please * wait until I've put it into the main eA branch which means I * believe it is ready for distribution. * * So, how do people use this? * * The first and most important function is defpattern * * defpattern 1, "[^:]+: (.*) loves (.*)", "label"; * * this defines a new pattern in set 1 using perl syntax * (http://www.troubleshooters.com/codecorn/littperl/perlreg.htm) * and tells it to jump to the supplied label when the pattern * is matched. * * each of the matched Groups will result in a variable being * set ($@p1$ through $@p9$ with $@p0$ being the entire string) * before the script gets executed. * * activatepset 1; * * This activates a set of patterns.. You can have many pattern * sets defined and many active all at once. This feature allows * you to set up "conversations" and ever changing expectations of * the pattern matcher * * deactivatepset 1; * * turns off a pattern set; * * deactivatepset -1; * * turns off ALL pattern sets; * * deletepset 1; * * deletes a pset */ /* Structure containing all info associated with a single pattern block */ struct pcrematch_entry { struct pcrematch_entry *next_; char *pattern_; pcre *pcre_; pcre_extra *pcre_extra_; char *label_; }; /* A set of patterns that can be activated and deactived with a single command */ struct pcrematch_set { struct pcrematch_set *next_, *prev_; struct pcrematch_entry *head_; int setid_; }; /* * Entire data structure hung off a NPC * * The reason I have done it this way (a void * in npc_data and then * this) was to reduce the number of patches that needed to be applied * to a ragnarok distribution to bring this code online. I * also wanted people to be able to grab this one file to get updates * without having to do a large number of changes. */ struct npc_parse { struct pcrematch_set *active_; struct pcrematch_set *inactive_; }; /** * delete everythign associated with a entry * * This does NOT do the list management */ void finalize_pcrematch_entry(struct pcrematch_entry *e) { //TODO: For some odd reason this causes a already-free'd error under Windows, but not *nix! [Skotlex] #ifndef _WIN32 if (e->pcre_) { free(e->pcre_); e->pcre_ = NULL; } #endif if (e->pcre_extra_) { free(e->pcre_extra_); e->pcre_ = NULL; } aFree(e->pattern_); aFree(e->label_); } /** * Lookup (and possibly create) a new set of patterns by the set id */ static struct pcrematch_set * lookup_pcreset(struct npc_data *nd,int setid) { struct pcrematch_set *pcreset; struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; if (npcParse == NULL) nd->chatdb = npcParse = (struct npc_parse *) aCalloc(sizeof(struct npc_parse), 1); pcreset = npcParse->active_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } if (pcreset == NULL) pcreset = npcParse->inactive_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } if (pcreset == NULL) { pcreset = (struct pcrematch_set *) aCalloc(sizeof(struct pcrematch_set), 1); pcreset->next_ = npcParse->inactive_; if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset; pcreset->prev_ = 0; npcParse->inactive_ = pcreset; pcreset->setid_ = setid; } return pcreset; } /** * activate a set of patterns. * * if the setid does not exist, this will silently return */ static void activate_pcreset(struct npc_data *nd,int setid) { struct pcrematch_set *pcreset; struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; if (npcParse == NULL) return; // Nothing to activate... pcreset = npcParse->inactive_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } if (pcreset == NULL) return; // not in inactive list if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset->prev_; if (pcreset->prev_ != NULL) pcreset->prev_->next_ = pcreset->next_; else npcParse->inactive_ = pcreset->next_; pcreset->prev_ = NULL; pcreset->next_ = npcParse->active_; if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset; npcParse->active_ = pcreset; } /** * deactivate a set of patterns. * * if the setid does not exist, this will silently return */ static void deactivate_pcreset(struct npc_data *nd,int setid) { struct pcrematch_set *pcreset; struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; if (npcParse == NULL) return; // Nothing to deactivate... if (setid == -1) { while(npcParse->active_ != NULL) deactivate_pcreset(nd, npcParse->active_->setid_); return; } pcreset = npcParse->active_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } if (pcreset == NULL) return; // not in active list if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset->prev_; if (pcreset->prev_ != NULL) pcreset->prev_->next_ = pcreset->next_; else npcParse->active_ = pcreset->next_; pcreset->prev_ = NULL; pcreset->next_ = npcParse->inactive_; if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset; npcParse->inactive_ = pcreset; } /** * delete a set of patterns. */ static void delete_pcreset(struct npc_data *nd,int setid) { int active = 1; struct pcrematch_set *pcreset; struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; if (npcParse == NULL) return; // Nothing to deactivate... pcreset = npcParse->active_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } if (pcreset == NULL) { active = 0; pcreset = npcParse->inactive_; while (pcreset != NULL) { if (pcreset->setid_ == setid) break; pcreset = pcreset->next_; } } if (pcreset == NULL) return; if (pcreset->next_ != NULL) pcreset->next_->prev_ = pcreset->prev_; if (pcreset->prev_ != NULL) pcreset->prev_->next_ = pcreset->next_; if(active) npcParse->active_ = pcreset->next_; else npcParse->inactive_ = pcreset->next_; pcreset->prev_ = NULL; pcreset->next_ = NULL; while (pcreset->head_) { struct pcrematch_entry *n = pcreset->head_->next_; finalize_pcrematch_entry(pcreset->head_); aFree(pcreset->head_); // Cleanin' the last ones.. [Lance] pcreset->head_ = n; } aFree(pcreset); } /** * create a new pattern entry */ static struct pcrematch_entry *create_pcrematch_entry(struct pcrematch_set * set) { struct pcrematch_entry * e = (struct pcrematch_entry *) aCalloc(sizeof(struct pcrematch_entry), 1); struct pcrematch_entry * last = set->head_; // Normally we would have just stuck it at the end of the list but // this doesn't sink up with peoples usage pattern. They wanted // the items defined first to have a higher priority then the // items defined later.. as a result, we have to do some work up // front.. /* if we are the first pattern, stick us at the end */ if (last == NULL) { set->head_ = e; return e; } /* Look for the last entry */ while (last->next_ != NULL) last = last->next_; last->next_ = e; e->next_ = NULL; return e; } /** * define/compile a new pattern */ void npc_chat_def_pattern(struct npc_data *nd, int setid, const char *pattern, const char *label) { const char *err; int erroff; struct pcrematch_set * s = lookup_pcreset(nd, setid); struct pcrematch_entry *e = create_pcrematch_entry(s); e->pattern_ = aStrdup(pattern); e->label_ = aStrdup(label); e->pcre_ = pcre_compile(pattern, PCRE_CASELESS, &err, &erroff, NULL); e->pcre_extra_ = pcre_study(e->pcre_, 0, &err); } /** * Delete everything associated with a NPC concerning the pattern * matching code * * this could be more efficent but.. how often do you do this? */ void npc_chat_finalize(struct npc_data *nd) { struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; if (npcParse == NULL) return; while(npcParse->active_) delete_pcreset(nd, npcParse->active_->setid_); while(npcParse->inactive_) delete_pcreset(nd, npcParse->inactive_->setid_); // Additional cleaning up [Lance] aFree(npcParse); } /** * Handler called whenever a global message is spoken in a NPC's area */ int npc_chat_sub(struct block_list *bl, va_list ap) { struct npc_data *nd = (struct npc_data *)bl; struct npc_parse *npcParse = (struct npc_parse *) nd->chatdb; char *msg; int len, pos, i; struct map_session_data *sd; struct npc_label_list *lst; struct pcrematch_set *pcreset; // Not interested in anything you might have to say... if (npcParse == NULL || npcParse->active_ == NULL) return 0; msg = va_arg(ap,char*); len = va_arg(ap,int); sd = va_arg(ap,struct map_session_data *); // grab the active list pcreset = npcParse->active_; // interate across all active sets while (pcreset != NULL) { struct pcrematch_entry *e = pcreset->head_; // interate across all patterns in that set while (e != NULL) { int offsets[20]; char buf[255]; // perform pattern match int r = pcre_exec(e->pcre_, e->pcre_extra_, msg, len, 0, 0, offsets, sizeof(offsets) / sizeof(offsets[0])); if (r >= 0) { // save out the matched strings switch (r) { case 10: memcpy(buf, &msg[offsets[18]], offsets[19]); buf[offsets[19]] = '\0'; set_var(sd, "$@p9$", buf); case 9: memcpy(buf, &msg[offsets[16]], offsets[17]); buf[offsets[17]] = '\0'; set_var(sd, "$@p8$", buf); case 8: memcpy(buf, &msg[offsets[14]], offsets[15]); buf[offsets[15]] = '\0'; set_var(sd, "$@p7$", buf); case 7: memcpy(buf, &msg[offsets[12]], offsets[13]); buf[offsets[13]] = '\0'; set_var(sd, "$@p6$", buf); case 6: memcpy(buf, &msg[offsets[10]], offsets[11]); buf[offsets[11]] = '\0'; set_var(sd, "$@p5$", buf); case 5: memcpy(buf, &msg[offsets[8]], offsets[9]); buf[offsets[9]] = '\0'; set_var(sd, "$@p4$", buf); case 4: memcpy(buf, &msg[offsets[6]], offsets[7]); buf[offsets[7]] = '\0'; set_var(sd, "$@p3$", buf); case 3: memcpy(buf, &msg[offsets[4]], offsets[5]); buf[offsets[5]] = '\0'; set_var(sd, "$@p2$", buf); case 2: memcpy(buf, &msg[offsets[2]], offsets[3]); buf[offsets[3]] = '\0'; set_var(sd, "$@p1$", buf); case 1: memcpy(buf, &msg[offsets[0]], offsets[1]); buf[offsets[1]] = '\0'; set_var(sd, "$@p0$", buf); } // find the target label.. this sucks.. lst=nd->u.scr.label_list; pos = -1; for (i = 0; i < nd->u.scr.label_list_num; i++) { if (strncmp(lst[i].name, e->label_, sizeof(lst[i].name)) == 0) { pos = lst[i].pos; break; } } if (pos == -1) { ShowWarning("Unable to find label: %s", e->label_); // unable to find label... do something.. return 0; } // run the npc script run_script(nd->u.scr.script,pos,sd->bl.id,nd->bl.id); return 0; } e = e->next_; } pcreset = pcreset->next_; } return 0; } int mob_chat_sub(struct block_list *bl, va_list ap){ struct mob_data *md = (struct mob_data *)bl; if(md->nd){ npc_chat_sub(&md->nd->bl, ap); } return 0; } // Various script builtins used to support these functions int buildin_defpattern(struct script_state *st) { int setid=conv_num(st,& (st->stack->stack_data[st->start+2])); const char *pattern=conv_str(st,& (st->stack->stack_data[st->start+3])); const char *label=conv_str(st,& (st->stack->stack_data[st->start+4])); struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); npc_chat_def_pattern(nd, setid, pattern, label); return 0; } int buildin_activatepset(struct script_state *st) { int setid=conv_num(st,& (st->stack->stack_data[st->start+2])); struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); activate_pcreset(nd, setid); return 0; } int buildin_deactivatepset(struct script_state *st) { int setid=conv_num(st,& (st->stack->stack_data[st->start+2])); struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); deactivate_pcreset(nd, setid); return 0; } int buildin_deletepset(struct script_state *st) { int setid=conv_num(st,& (st->stack->stack_data[st->start+2])); struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); delete_pcreset(nd, setid); return 0; } #endif //PCRE_SUPPORT