/* CrossFire, A Multiplayer game for X-windows Copyright (C) 2008 Crossfire Development Team Copyright (C) 1992 Frank Tore Johansen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The authors can be reached via e-mail at crossfire-devel@real-time.com */ /** @file Structures and functions used for the @ref page_dialog "dialog system". */ #include #include "global.h" #include "define.h" #include "object.h" #include "dialog.h" /** * Frees obj::dialog_information. * @param op what to clean for. */ void free_dialog_information(object *op) { struct_dialog_message *current, *next; struct_dialog_reply *currep, *nextrep; if (!QUERY_FLAG(op, FLAG_DIALOG_PARSED)) return; CLEAR_FLAG(op, FLAG_DIALOG_PARSED); if (!op->dialog_information) return; current = op->dialog_information->all_messages; while (current) { next = current->next; free(current->match); free(current->message); currep = current->replies; while (currep) { nextrep = currep->next; free(currep->reply); free(currep->message); currep = nextrep; } free(current); current = next; } currep = op->dialog_information->all_replies; while (currep) { nextrep = currep->next; free(currep->reply); free(currep->message); free(currep); currep = nextrep; } free(op->dialog_information); op->dialog_information = NULL; } /** * Does the text match the expression? * @param exp expression to try to match. * @param text what to test. * @return 1 if match, 0 else. * @todo better * handling (incorrect now, will match even if trailing chars) */ static int matches(const char *exp, const char *text) { char *pipe, *save, *msg; int match = 0; if (exp[0] == '*') return 1; msg = strdup(exp); pipe = strtok_r(msg, "|", &save); while (pipe) { if (re_cmp(text, pipe)) { match = 1; break; } pipe = strtok_r(NULL, "|", &save); } free(msg); return match; } /** * Parse the dialog information for op, and fills in obj::dialog_information. * Can be called safely multiple times (will just ignore the other calls). * * @param op object to parse the obj::msg field. */ static void parse_dialog_information(object *op) { struct_dialog_message *message = NULL, *last = NULL; struct_dialog_reply *reply = NULL; char *current, *save, *msg, *cp; int len; /* Used for constructing message with */ char *tmp = NULL; size_t tmplen = 0; if (QUERY_FLAG(op, FLAG_DIALOG_PARSED)) return; SET_FLAG(op, FLAG_DIALOG_PARSED); op->dialog_information = (struct_dialog_information *)calloc(1, sizeof(struct_dialog_information)); if (!op->msg) return; msg = strdup(op->msg); current = strtok_r(msg, "\n", &save); while (current) { if (strncmp(current, "@match ", 7) == 0) { if (message) { message->message = tmp; tmp = NULL; tmplen = 0; } message = (struct_dialog_message *)calloc(1, sizeof(struct_dialog_message)); if (last) last->next = message; else op->dialog_information->all_messages = message; last = message; message->match = strdup(current+7); } else if ((strncmp(current, "@reply ", 7) == 0 && (len = 7)) || (strncmp(current, "@question ", 10) == 0 && (len = 10))) { if (message) { reply = (struct_dialog_reply *)calloc(1, sizeof(struct_dialog_reply)); reply->type = (len == 7 ? rt_reply : rt_question); cp = strchr(current+len, ' '); if (cp) { *cp = '\0'; reply->reply = strdup(current+len); reply->message = strdup(cp+1); } else { reply->reply = strdup(current+len); reply->message = strdup(reply->reply); LOG(llevDebug, "Warning: @reply/@question without message for %s!\n", op->name); } reply->next = message->replies; message->replies = reply; reply = (struct_dialog_reply *)calloc(1, sizeof(struct_dialog_reply)); reply->reply = strdup(message->replies->reply); reply->message = strdup(message->replies->message); reply->type = message->replies->type; reply->next = op->dialog_information->all_replies; op->dialog_information->all_replies = reply; } else LOG(llevDebug, "Warning: @reply not in @match block for %s!\n", op->name); } else if (message) { /* Needed to set initial \0 */ int wasnull = FALSE; tmplen += strlen(current)+2; if (!tmp) wasnull = TRUE; tmp = realloc(tmp, tmplen*sizeof(char)); if (!tmp) fatal(OUT_OF_MEMORY); if (wasnull) tmp[0] = 0; strncat(tmp, current, tmplen-strlen(tmp)-1); strncat(tmp, "\n", tmplen-strlen(tmp)-1); } current = strtok_r(NULL, "\n", &save); } if (message) { if (!tmp) message->message = strdup(""); else message->message = tmp; tmp = NULL; tmplen = 0; } free(msg); } /** * Tries to find a message matching the said text. * @param op who is being talked to. * @param text what is being said. * @param[out] message what op should say. Won't be NULL if return is 1. * @param[out] reply text the one talking should say based on the text. Can be NULL. * @return 0 if no match, 1 if a message did match the text. * @todo smarter match, try to find exact before joker (*) one. */ int get_dialog_message(object *op, const char *text, struct_dialog_message **message, struct_dialog_reply **reply) { if (!QUERY_FLAG(op, FLAG_DIALOG_PARSED)) parse_dialog_information(op); for (*message = op->dialog_information->all_messages; *message; *message = (*message)->next) { if (matches((*message)->match, text)) { break; } } if (!*message) return 0; for (*reply = op->dialog_information->all_replies; *reply; *reply = (*reply)->next) { if (strcmp((*reply)->reply, text) == 0) break; } return 1; }