[1] | 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 |
---|