npc_chat.cpp 11 KB

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