//***************************************************************************** // // alias.c // // Aliases allow a player to set up one command as another (e.g. "eat bread" // as "food"). // //***************************************************************************** #include "../mud.h" #include "../utils.h" #include "../auxiliary.h" #include "../storage.h" #include "../character.h" #include "../socket.h" #include "../action.h" #include "../hooks.h" #include "alias.h" //***************************************************************************** // mandatory modules //***************************************************************************** #include "../scripts/scripts.h" #include "../scripts/pychar.h" //***************************************************************************** // auxiliary data //***************************************************************************** typedef struct alias_aux_data { HASHTABLE *aliases; int alias_queue; } ALIAS_AUX_DATA; ALIAS_AUX_DATA * newAliasAuxData() { ALIAS_AUX_DATA *data = calloc(1, sizeof(ALIAS_AUX_DATA)); // // Hashtables can take up lots of storage space. Because of this, let's // not create any tables until it's actually needed. This will cut down // on lots of memory usage w.r.t. NPCs who do not use aliases // data->aliases = newHashtable(); // return data; } void deleteAliasAuxData(ALIAS_AUX_DATA *data) { if(data->aliases) { HASH_ITERATOR *hash_i = newHashIterator(data->aliases); const char *alias = NULL; char *cmd = NULL; ITERATE_HASH(alias, cmd, hash_i) free(cmd); deleteHashIterator(hash_i); deleteHashtable(data->aliases); } free(data); } void aliasAuxDataCopyTo(ALIAS_AUX_DATA *from, ALIAS_AUX_DATA *to) { // aliases are for personal use, and it is doubtful we would // ever have a motivation for copying them over. Thus, we shall // not do any copying here. } ALIAS_AUX_DATA * aliasAuxDataCopy(ALIAS_AUX_DATA *data) { // as with aliasAuxDataCopyTo, there seems to be little reason // why we would ever want to copy aliases. They are personal, // and have no reason for being copied. Thus, let us just return // a new datastructure. return newAliasAuxData(); } STORAGE_SET *aliasAuxDataStore(ALIAS_AUX_DATA *data) { // if we have no hashtable, return an empty set if(data->aliases == NULL || hashSize(data->aliases) == 0) return new_storage_set(); STORAGE_SET *set = new_storage_set(); STORAGE_SET_LIST *list = new_storage_list(); HASH_ITERATOR *hash_i = newHashIterator(data->aliases); const char *name = NULL; const char *cmd = NULL; store_list(set, "aliases", list); ITERATE_HASH(name, cmd, hash_i) { STORAGE_SET *alias_set = new_storage_set(); store_string(alias_set, "key", name); store_string(alias_set, "val", hashIteratorCurrentVal(hash_i)); storage_list_put(list, alias_set); } deleteHashIterator(hash_i); return set; } ALIAS_AUX_DATA *aliasAuxDataRead(STORAGE_SET *set) { // if the file contains no alias data, then don't load any if(!storage_contains(set, "aliases")) return newAliasAuxData(); ALIAS_AUX_DATA *data = newAliasAuxData(); data->aliases = newHashtable(); STORAGE_SET_LIST *list = read_list(set, "aliases"); STORAGE_SET *var = NULL; while( (var = storage_list_next(list)) != NULL) hashPut(data->aliases, read_string(var, "key"), strdup(read_string(var, "val"))); return data; } //***************************************************************************** // functions for interacting with character aliases //***************************************************************************** LIST *charGetAliases(CHAR_DATA *ch) { ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); if(data->aliases == NULL) return newList(); return hashCollect(data->aliases); } const char *charGetAlias(CHAR_DATA *ch, const char *alias) { ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); if(data->aliases == NULL) return NULL; return hashGet(data->aliases, alias); } void charClearAliases(CHAR_DATA *ch) { LIST *aliases = charGetAliases(ch); LIST_ITERATOR *alias_i = newListIterator(aliases); const char *alias = NULL; ITERATE_LIST(alias, alias_i) { charSetAlias(ch, alias, NULL); } deleteListIterator(alias_i); deleteListWith(aliases, free); } void charSetAlias(CHAR_DATA *ch, const char *alias, const char *cmd){ ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); // if our alias table is NULL, create a new one if(data->aliases == NULL) data->aliases = newHashtable(); // pull out the last one char *oldcmd = hashRemove(data->aliases, alias); if(oldcmd != NULL) free(oldcmd); // put in the new one if it exists if(cmd && *cmd) { hashPut(data->aliases, alias, strdup(cmd)); } } int charGetAliasesQueued(CHAR_DATA *ch) { ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); return data->alias_queue; } void charSetAliasesQueued(CHAR_DATA *ch, int amnt) { ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); data->alias_queue = amnt; } BUFFER *expand_alias(CHAR_DATA *ch, const char *alias, const char *arg) { static int func_depth = 0; static int MAX_ALIAS_DEPTH = 10; BUFFER *cmd = newBuffer(SMALL_BUFFER); func_depth++; BUFFER *filled_alias = newBuffer(SMALL_BUFFER); bufferCat(filled_alias, alias); // now, replace all of our parameters int i; for(i = 1; i < 10; i++) { char param[3]; char one_arg[SMALL_BUFFER]; sprintf(param, "$%d", i); arg_num(arg, one_arg, i); bufferReplace(filled_alias, param, one_arg, TRUE); } // then we replace the wildcard bufferReplace(filled_alias, "$*", arg, TRUE); alias = bufferString(filled_alias); if(func_depth <= MAX_ALIAS_DEPTH) { for(i = 0; alias[i] != '\0'; i++) { // do we have an embedded alias or not? if(alias[i] != '[') bprintf(cmd, "%c", alias[i]); else { // figure out the start and end of the embedded alias int start = i; int depth = 1; for(i++; alias[i] != '\0' && depth > 0; i++) { if(alias[i] == '[') depth++; else if(alias[i] == ']') depth--; } // only cat something if we closed the alias off if(depth == 0) { // make a copy of it, minus the opening and closing braces char *newstring = strdup(alias+start+1); newstring[i-start-2] = '\0'; char newcmd[SMALL_BUFFER]; char *newarg = one_arg(newstring, newcmd); const char *format = charGetAlias(ch, newcmd); // do we have a format? if so, expand the new alias and cat it in if(format != NULL) { BUFFER *newbuf = expand_alias(ch, format, newarg); bufferCat(cmd, bufferString(newbuf)); deleteBuffer(newbuf); } // clean up our mess free(newstring); } // because we'll be incrementing on the next go around the loop i--; } } } func_depth--; deleteBuffer(filled_alias); return cmd; } //***************************************************************************** // character commands //***************************************************************************** // // Set or delete an alias. If no argument is supplied, all aliases are listed. COMMAND(cmd_alias) { // list off all of the aliases if(!arg || !*arg) { ALIAS_AUX_DATA *data = charGetAuxiliaryData(ch, "alias_aux_data"); send_to_char(ch, "Current aliases:\r\n"); if(data->aliases == NULL || hashSize(data->aliases) == 0) send_to_char(ch, " none\r\n"); else { LIST *keys = hashCollect(data->aliases); listSortWith(keys, strcasecmp); LIST_ITERATOR *key_i = newListIterator(keys); const char *key = NULL; ITERATE_LIST(key, key_i) { send_to_char(ch, " %-20s %s\r\n", key, (char *)hashGet(data->aliases, key)); } deleteListIterator(key_i); deleteListWith(keys, free); } } else { char alias[SMALL_BUFFER]; arg = one_arg(arg, alias); // try to delete an alias if(!arg || !*arg) { const char *curr_cmd = charGetAlias(ch, alias); if(!curr_cmd) send_to_char(ch, "You do not have such an alias.\r\n"); else { charSetAlias(ch, alias, arg); } } // try to set an alias else { charSetAlias(ch, alias, arg); send_to_char(ch, "Alias set.\r\n"); } } } //***************************************************************************** // Python extensions //***************************************************************************** PyObject *PyChar_GetAlias(PyObject *self, PyObject *args) { char *alias = NULL; CHAR_DATA *ch = NULL; if(!PyArg_ParseTuple(args, "s", &alias)) { PyErr_Format(PyExc_TypeError, "aliases must be string names."); return NULL; } if( (ch = PyChar_AsChar(self)) == NULL) { PyErr_Format(PyExc_TypeError, "Character %d does not exist.", PyChar_AsUid(self)); return NULL; } return Py_BuildValue("z", charGetAlias(ch, alias)); } PyObject *PyChar_SetAlias(PyObject *self, PyObject *args) { char *cmd = NULL; char *alias = NULL; CHAR_DATA *ch = NULL; if(!PyArg_ParseTuple(args, "sz", &alias, &cmd)) { PyErr_Format(PyExc_TypeError, "aliases and commands must be string names."); return NULL; } if( (ch = PyChar_AsChar(self)) == NULL) { PyErr_Format(PyExc_TypeError, "Character %d does not exist.", PyChar_AsUid(self)); return NULL; } charSetAlias(ch, alias, cmd); return Py_BuildValue("i", 1); } PyObject *PyChar_GetAliases(PyObject *self, void *closure) { CHAR_DATA *ch = NULL; if((ch = PyChar_AsChar(self)) == NULL) { PyErr_Format(PyExc_TypeError, "Character %d does not exist.", PyChar_AsUid(self)); return NULL; } PyObject *pylist = PyList_New(0); LIST *list = charGetAliases(ch); LIST_ITERATOR *alias_i = newListIterator(list); const char *alias = NULL; ITERATE_LIST(alias, alias_i) { PyObject *str = Py_BuildValue("s", alias); PyList_Append(pylist, str); Py_DECREF(str); } deleteListIterator(alias_i); deleteListWith(list, free); return pylist; } //***************************************************************************** // implementation of alias.h //***************************************************************************** void init_aliases() { // install aliases on the character datastructure auxiliariesInstall("alias_aux_data", newAuxiliaryFuncs(AUXILIARY_TYPE_CHAR, newAliasAuxData, deleteAliasAuxData, aliasAuxDataCopyTo, aliasAuxDataCopy, aliasAuxDataStore, aliasAuxDataRead)); // allow people to view their aliases add_cmd("alias", NULL, cmd_alias, "player", TRUE); // Python extensions PyChar_addMethod("get_alias", PyChar_GetAlias, METH_VARARGS, "get_alias(name)\n" "\n" "Return character's alias by the specified name, or None."); PyChar_addMethod("set_alias", PyChar_SetAlias, METH_VARARGS, "set_alias(name, value)\n" "\n" "Set a character's alias. Value must be a string."); PyChar_addGetSetter("aliases",PyChar_GetAliases, NULL, "A list of all aliases the character currently has defined. Immutable."); } bool try_alias(CHAR_DATA *ch, char *command, char *arg) { // is this command from an alias that executes multi commands? // if it is, don't let it trigger any further aliases, or else we might // get stuck in an infinite loop bool aliases_ok = TRUE; int aliases_queued = charGetAliasesQueued(ch); if(aliases_queued > 0) { // this command came from an alias. ergo, there is going to be no echo // of the command. send the socket a newline so we don't print on the // person's prompt send_to_char(ch, "\r\n"); aliases_ok = FALSE; } // make sure it didn't come from another alias if(!aliases_ok) return FALSE; else { const char *alias = charGetAlias(ch, command); // see if the alias exists if(alias == NULL) return FALSE; else { // break the buffer contents up into multiple commands, if there are any BUFFER *buf = expand_alias(ch, alias, arg); LIST *cmds = parse_strings(bufferString(buf), ';'); char *first = listPop(cmds); // queue all of our commands after the first onto the command list if(charGetSocket(ch) && listSize(cmds) > 0) { LIST_ITERATOR *cmd_i = newListIterator(cmds); char *cmd = NULL; ITERATE_LIST(cmd, cmd_i) { socketQueueCommand(charGetSocket(ch), cmd); } deleteListIterator(cmd_i); charSetAliasesQueued(ch, listSize(cmds) + 1); } if(first != NULL) do_cmd(ch, first, FALSE); deleteListWith(cmds, free); if(first) free(first); deleteBuffer(buf); return TRUE; } } }