//***************************************************************************** // // pymud.h // // a python module that provides some useful utility functions for interacting // with the MUD. Includes stuff like global variables, messaging functions, // and a whole bunch of other stuff. // // WORK FOR FUTURE: Our use of Py_INCREF is probably creating a // memory leak somewhere. // //***************************************************************************** #include <Python.h> #include <structmember.h> #include "../mud.h" #include "../utils.h" #include "../character.h" #include "../inform.h" #include "../handler.h" #include "../parse.h" #include "../races.h" #include "scripts.h" #include "pyroom.h" #include "pychar.h" #include "pyobj.h" #include "pyplugs.h" #include "pyexit.h" #include "pysocket.h" //***************************************************************************** // local variables and functions //***************************************************************************** // global variables we have set. PyObject *globals = NULL; // a list of methods to add to the mud module LIST *pymud_methods = NULL; //***************************************************************************** // // GLOBAL VARIABLES // // the following functions allow scriptors to store/access global variables. // globals are stored in a python map, that maps two python objects together. // the functions used to interact with globals are: // get_global(key) // set_global(key, val) // erase_global(key) // //***************************************************************************** PyObject *mud_get_global(PyObject *self, PyObject *args) { PyObject *key = NULL; // get the key if (!PyArg_ParseTuple(args, "O", &key)) { PyErr_Format(PyExc_TypeError, "Could not retrieve global variable - no key provided"); return NULL; } PyObject *val = PyDict_GetItem(globals, key); if(val == NULL) val = Py_None; Py_INCREF(val); return val; } PyObject *mud_set_global(PyObject *self, PyObject *args) { PyObject *key = NULL, *val = NULL; if (!PyArg_ParseTuple(args, "OO", &key, &val)) { PyErr_Format(PyExc_TypeError, "Could not set global variable - need key and value"); return NULL; } PyDict_SetItem(globals, key, val); return Py_BuildValue("i", 1); } PyObject *mud_erase_global(PyObject *self, PyObject *args) { PyObject *key = NULL; if (!PyArg_ParseTuple(args, "O", &key)) { PyErr_Format(PyExc_TypeError, "Could not erase global variable - need key"); return NULL; } PyDict_SetItem(globals, key, Py_None); return Py_BuildValue("i", 1); } // // format a string to be into a typical description style PyObject *mud_format_string(PyObject *self, PyObject *args) { char *string = NULL; bool indent = TRUE; int width = SCREEN_WIDTH; // parse all of the values if (!PyArg_ParseTuple(args, "s|bi", &string, &indent, &width)) { PyErr_Format(PyExc_TypeError, "Can not format non-string values."); return NULL; } // dup the string so we can work with it and not intrude on the PyString data BUFFER *buf = newBuffer(MAX_BUFFER); bufferCat(buf, string); bufferFormat(buf, width, (indent ? PARA_INDENT : 0)); PyObject *ret = Py_BuildValue("s", bufferString(buf)); deleteBuffer(buf); return ret; } // // parses arguments for character commands PyObject *mud_parse_args(PyObject *self, PyObject *args) { PyObject *pych = NULL; bool show_errors = FALSE; char *cmd = NULL; char *pyargs = NULL; char *syntax = NULL; char *parse_args = NULL; CHAR_DATA *ch = NULL; // parse our arguments if(!PyArg_ParseTuple(args, "Obsss", &pych, &show_errors, &cmd, &pyargs, &syntax)) { PyErr_Format(PyExc_TypeError, "Invalid arguments to parse_args"); return NULL; } // convert the character if(!PyChar_Check(pych) || (ch = PyChar_AsChar(pych)) == NULL) { PyErr_Format(PyExc_TypeError, "First argument must be an existent character!"); return NULL; } // strdup our py args; they might be edited in the parse function parse_args = strdup(pyargs); // finish up and garbage collections PyObject *retval = Py_parse_args(ch, show_errors, cmd, parse_args, syntax); free(parse_args); return retval; } // // a wrapper around NakedMud's generic_find() function PyObject *mud_generic_find(PyObject *self, PyObject *args) { PyObject *py_looker = Py_None; CHAR_DATA *looker = NULL; char *type_str = NULL; bitvector_t type = 0; char *scope_str = NULL; bitvector_t scope = 0; char *arg = NULL; bool all_ok = TRUE; // parse the arguments if(!PyArg_ParseTuple(args, "Osss|b", &py_looker, &arg, &type_str, &scope_str, &all_ok)) { PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to mud.generic_find()"); return NULL; } // convert the looker if(py_looker != Py_None) { if(!PyChar_Check(py_looker) || (looker = PyChar_AsChar(py_looker)) == NULL){ PyErr_Format(PyExc_TypeError, "First argument must be an existent character or None!"); return NULL; } } // convert the scope if(is_keyword(scope_str, "room", FALSE)) SET_BIT(scope, FIND_SCOPE_ROOM); if(is_keyword(scope_str, "inv", FALSE)) SET_BIT(scope, FIND_SCOPE_INV); if(is_keyword(scope_str, "worn", FALSE)) SET_BIT(scope, FIND_SCOPE_WORN); if(is_keyword(scope_str, "world", FALSE)) SET_BIT(scope, FIND_SCOPE_WORLD); if(is_keyword(scope_str, "visible", FALSE)) SET_BIT(scope, FIND_SCOPE_VISIBLE); if(is_keyword(scope_str, "immediate", FALSE)) SET_BIT(scope, FIND_SCOPE_IMMEDIATE); if(is_keyword(scope_str, "all", FALSE)) SET_BIT(scope, FIND_SCOPE_ALL); // convert the types if(is_keyword(type_str, "obj", FALSE)) SET_BIT(type, FIND_TYPE_OBJ); if(is_keyword(type_str, "char", FALSE)) SET_BIT(type, FIND_TYPE_CHAR); if(is_keyword(type_str, "exit", FALSE)) SET_BIT(type, FIND_TYPE_EXIT); if(is_keyword(type_str, "in", FALSE)) SET_BIT(type, FIND_TYPE_IN_OBJ); if(is_keyword(type_str, "all", FALSE)) SET_BIT(type,FIND_TYPE_OBJ | FIND_TYPE_CHAR | FIND_TYPE_EXIT | FIND_TYPE_IN_OBJ); // do the search int found_type = FOUND_NONE; void *found = generic_find(looker, arg, type, scope, all_ok, &found_type); if(found_type == FOUND_CHAR) { // were we searching for one type, or multiple types? if(!strcasecmp("char", type_str)) return Py_BuildValue("O", charGetPyFormBorrowed(found)); else return Py_BuildValue("Os", charGetPyFormBorrowed(found), "char"); } else if(found_type == FOUND_EXIT) { // were we searching for one type, or multiple types? PyObject *exit = newPyExit(found); PyObject *retval = NULL; if(!strcasecmp("exit", type_str)) retval = Py_BuildValue("O", exit); else retval = Py_BuildValue("Os", exit, "obj"); Py_DECREF(exit); return retval; } else if(found_type == FOUND_OBJ) { // were we searching for one type, or multiple types? if(!strcasecmp("obj", type_str)) return Py_BuildValue("O", objGetPyFormBorrowed(found)); else return Py_BuildValue("Os", objGetPyFormBorrowed(found), "obj"); } else if(found_type == FOUND_IN_OBJ) { // were we searching for one type, or multiple types? if(!strcasecmp("in", type_str)) return Py_BuildValue("O", objGetPyFormBorrowed(found)); else return Py_BuildValue("Os", objGetPyFormBorrowed(found), "in"); } // now it gets a bit more tricky... we have to see what other bit was set else if(found_type == FOUND_LIST) { PyObject *list = PyList_New(0); LIST_ITERATOR *found_i = newListIterator(found); void *one_found = NULL; if(IS_SET(type, FIND_TYPE_CHAR)) { ITERATE_LIST(one_found, found_i) PyList_Append(list, charGetPyFormBorrowed(one_found)); } else if(IS_SET(type, FIND_TYPE_OBJ | FIND_TYPE_IN_OBJ)) { ITERATE_LIST(one_found, found_i) PyList_Append(list, objGetPyFormBorrowed(one_found)); } deleteListIterator(found_i); deleteList(found); PyObject *retval = Py_BuildValue("Os", list, "list"); Py_DECREF(list); return retval; } // nothing was found... return Py_BuildValue("Os", Py_None, Py_None); } // // execute message() from inform.h PyObject *mud_message(PyObject *self, PyObject *args) { // the python/C representations of the various variables that message() needs PyObject *pych = NULL; CHAR_DATA *ch = NULL; PyObject *pyvict = NULL; CHAR_DATA *vict = NULL; PyObject *pyobj = NULL; OBJ_DATA *obj = NULL; PyObject *pyvobj = NULL; OBJ_DATA *vobj = NULL; char *pyrange = NULL; bitvector_t range = 0; char *mssg = NULL; int hide_nosee = 0; // parse all of the arguments if(!PyArg_ParseTuple(args, "OOOObss", &pych, &pyvict, &pyobj, &pyvobj, &hide_nosee, &pyrange, &mssg)) { PyErr_Format(PyExc_TypeError,"Invalid arguments supplied to mud.message()"); return NULL; } // convert the character if(pych != Py_None) { if(!PyChar_Check(pych) || (ch = PyChar_AsChar(pych)) == NULL) { PyErr_Format(PyExc_TypeError, "First argument must be an existent character or None!"); return NULL; } } // convert the victim if(pyvict != Py_None) { if(!PyChar_Check(pyvict) || (vict = PyChar_AsChar(pyvict)) == NULL) { PyErr_Format(PyExc_TypeError, "Second argument must be an existent character or None!"); return NULL; } } // convert the object if(pyobj != Py_None) { if(!PyObj_Check(pyobj) || (obj = PyObj_AsObj(pyobj)) == NULL) { PyErr_Format(PyExc_TypeError, "Third argument must be an existent object or None!"); return NULL; } } // convert the target object if(pyvobj != Py_None) { if(!PyObj_Check(pyvobj) || (vobj = PyObj_AsObj(pyvobj)) == NULL) { PyErr_Format(PyExc_TypeError, "Fourth argument must be an existent object or None!"); return NULL; } } // check all of our keywords: char, vict, room if(is_keyword(pyrange, "to_char", FALSE)) SET_BIT(range, TO_CHAR); if(is_keyword(pyrange, "to_vict", FALSE)) SET_BIT(range, TO_VICT); if(is_keyword(pyrange, "to_room", FALSE)) SET_BIT(range, TO_ROOM); if(is_keyword(pyrange, "to_world", FALSE)) SET_BIT(range, TO_WORLD); // finally, send out the message message(ch, vict, obj, vobj, hide_nosee, range, mssg); return Py_BuildValue("i", 1); } // // extracts an mob or object from the game PyObject *mud_extract(PyObject *self, PyObject *args) { PyObject *thing = NULL; // parse the value if (!PyArg_ParseTuple(args, "O", &thing)) { PyErr_Format(PyExc_TypeError, "extract must be provided with an object or mob to extract!."); return NULL; } // check its type if(PyChar_Check(thing)) { CHAR_DATA *ch = PyChar_AsChar(thing); if(ch != NULL) extract_mobile(ch); else { PyErr_Format(PyExc_StandardError, "Tried to extract nonexistent character!"); return NULL; } } else if(PyObj_Check(thing)) { OBJ_DATA *obj = PyObj_AsObj(thing); if(obj != NULL) extract_obj(obj); else { PyErr_Format(PyExc_StandardError, "Tried to extract nonexistent object!"); return NULL; } } else if(PyRoom_Check(thing)) { ROOM_DATA *room = PyRoom_AsRoom(thing); if(room != NULL) extract_room(room); else { PyErr_Format(PyExc_StandardError, "Tried to extract nonexistent room!"); return NULL; } } // success return Py_BuildValue("i", 1); } // // functional form of if/then/else PyObject *mud_ite(PyObject *self, PyObject *args) { PyObject *condition = NULL; PyObject *true_act = NULL; PyObject *false_act = Py_None; if (!PyArg_ParseTuple(args, "OO|O", &condition, &true_act, &false_act)) { PyErr_Format(PyExc_TypeError, "ite must be specified 2 and an optional 3rd " "arg"); return NULL; } // check to see if our condition is true if( (PyInt_Check(condition) && PyInt_AsLong(condition) != 0) || (PyString_Check(condition) && strlen(PyString_AsString(condition)) > 0)) return true_act; else return false_act; } // // returns whether or not two database keys have the same name. Locale sensitive PyObject *mud_keys_equal(PyObject *self, PyObject *args) { char *key1 = NULL; char *key2 = NULL; if(!PyArg_ParseTuple(args, "ss", &key1, &key2)) { PyErr_Format(PyExc_TypeError, "keys_equal takes two string arguments"); return NULL; } char *fullkey1 = strdup(get_fullkey_relative(key1, get_script_locale())); char *fullkey2 = strdup(get_fullkey_relative(key2, get_script_locale())); bool ok = !strcasecmp(fullkey1, fullkey2); free(fullkey1); free(fullkey2); return Py_BuildValue("i", ok); } // // returns the mud's message of the day PyObject *mud_get_motd(PyObject *self, PyObject *args) { return Py_BuildValue("s", bufferString(motd)); } // // returns the mud's message of the day PyObject *mud_get_greeting(PyObject *self, PyObject *args) { return Py_BuildValue("s", bufferString(greeting)); } PyObject *mud_log_string(PyObject *self, PyObject *args) { char *mssg = NULL; if(!PyArg_ParseTuple(args, "s", &mssg)) { PyErr_Format(PyExc_TypeError, "a message must be supplied to log_string"); return NULL; } // we have to strip all %'s out of this message BUFFER *buf = newBuffer(1); bufferCat(buf, mssg); bufferReplace(buf, "%", "%%", TRUE); log_string(bufferString(buf)); deleteBuffer(buf); return Py_BuildValue("i", 1); } PyObject *mud_is_race(PyObject *self, PyObject *args) { char *race = NULL; bool player_only = FALSE; if(!PyArg_ParseTuple(args, "s|b", &race, &player_only)) { PyErr_Format(PyExc_TypeError, "a string must be supplied"); return NULL; } if(player_only) return Py_BuildValue("i", raceIsForPC(race)); else return Py_BuildValue("i", isRace(race)); } PyObject *mud_list_races(PyObject *self, PyObject *args) { bool player_only = FALSE; if(!PyArg_ParseTuple(args, "|b", &player_only)) { PyErr_Format(PyExc_TypeError, "true/false value to list only player races must be provided."); return NULL; } return Py_BuildValue("s", raceGetList(player_only)); } PyObject *mud_send(PyObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[ ] = { "list", "mssg", "dict", "newline", NULL }; PyObject *list = NULL; char *text = NULL; PyObject *dict = NULL; bool newline = TRUE; if(!PyArg_ParseTupleAndKeywords(args, kwds, "Os|Ob", kwlist, &list, &text, &dict, &newline)) { PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to mud.send"); return NULL; } // is dict None? set it to NULL for expand_to_char if(dict == Py_None) dict = NULL; // make sure the dictionary is a dictionary if(!(dict == NULL || PyDict_Check(dict))) { PyErr_Format(PyExc_TypeError, "mud.send expects third argument to be a dict object."); return NULL; } // make sure the list is a list if(!PyList_Check(list)) { PyErr_Format(PyExc_TypeError, "mud.send expects first argument to be a list of characters."); return NULL; } // go through our list of characters, and send each of them the message int i = 0; for(; i < PyList_Size(list); i++) { // make sure it's a character, and it has a socket PyObject *pych = PyList_GetItem(list, i); CHAR_DATA *ch = NULL; if(!PyChar_Check(pych)) continue; if( (ch = PyChar_AsChar(pych)) == NULL || charGetSocket(ch) == NULL) continue; if(dict != NULL) PyDict_SetItemString(dict, "ch", charGetPyFormBorrowed(ch)); expand_to_char(ch, text, dict, get_script_locale(), newline); } return Py_BuildValue(""); } PyObject *mud_expand_text(PyObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[ ] = { "text", "dict", "newline", NULL }; char *text = NULL; PyObject *dict = NULL; bool newline = FALSE; if(!PyArg_ParseTupleAndKeywords(args, kwds, "s|Ob", kwlist, &text, &dict, &newline)) { PyErr_Format(PyExc_TypeError, "Invalid arguments supplied to mud.expand_text"); return NULL; } // is dict None? set it to NULL for expand_to_char if(dict == Py_None) dict = NULL; // make sure the dictionary is a dictionary if(!(dict == NULL || PyDict_Check(dict))) { PyErr_Format(PyExc_TypeError, "mud.expand_text expects second argument to be a dict object."); return NULL; } // build our script environment BUFFER *buf = newBuffer(1); PyObject *env = restricted_script_dict(); if(dict != NULL) PyDict_Update(env, dict); // do the expansion bufferCat(buf, text); expand_dynamic_descs_dict(buf, env, get_script_locale()); if(newline == TRUE) bufferCat(buf, "\r\n"); // garbage collection and return PyObject *ret = Py_BuildValue("s", bufferString(buf)); Py_XDECREF(env); deleteBuffer(buf); return ret; } //***************************************************************************** // MUD module //***************************************************************************** void PyMud_addMethod(const char *name, void *f, int flags, const char *doc) { // make sure our list of methods is created if(pymud_methods == NULL) pymud_methods = newList(); // make the Method def PyMethodDef *def = calloc(1, sizeof(PyMethodDef)); def->ml_name = strdup(name); def->ml_meth = (PyCFunction)f; def->ml_flags = flags; def->ml_doc = (doc ? strdup(doc) : NULL); listPut(pymud_methods, def); } PyMODINIT_FUNC init_PyMud(void) { // add all of our methods PyMud_addMethod("get_global", mud_get_global, METH_VARARGS, "get_global(name)\n\n" "Return a non-persistent global variable, or None."); PyMud_addMethod("set_global", mud_set_global, METH_VARARGS, "set_global(name, val)\n\n" "Sets a non-persistent global variable. Val can be any type."); PyMud_addMethod("erase_global", mud_erase_global, METH_VARARGS, "erase_global(name)\n\n" "Delete a value from the global variable table."); PyMud_addMethod("message", mud_message, METH_VARARGS, "message(ch, vict, obj, vobj, show_invis, range, mssg)\n\n" "Send a message via the mud messaging system using $ expansions. Range\n" "can be 'to_room', 'to_char', 'to_vict', or 'to_world'."); PyMud_addMethod("format_string", mud_format_string, METH_VARARGS, "format_string(text, indent=True, width=80)\n\n" "Format a block of text to be of the specified width, possibly indenting\n" "paragraphs."); PyMud_addMethod("generic_find", mud_generic_find, METH_VARARGS, "Deprecated. Use mud.parse_args instead."); PyMud_addMethod("extract", mud_extract, METH_VARARGS, "extract(thing)\n\n" "Extracts an object, character, or room from the game."); PyMud_addMethod("keys_equal", mud_keys_equal, METH_VARARGS, "keys_equal(key1, key2)\n\n" "Returns whether two world database keys are equal, relative to the\n" "locale (if any) that the current script is running in."); PyMud_addMethod("ite", mud_ite, METH_VARARGS, "ite(logic_statement, if_statement, else_statement=None)\n\n" "A functional form of if/then/else."); PyMud_addMethod("parse_args", mud_parse_args, METH_VARARGS, "parse_args(ch, show_usage_errors, cmd, args, format)\n\n" "equivalent to parse_args written in C. See parse.h for information."); PyMud_addMethod("get_motd", mud_get_motd, METH_NOARGS, "get_motd()\n\n" "Returns the mud's message of the day."); PyMud_addMethod("get_greeting", mud_get_greeting, METH_NOARGS, "get_greeting()\n\n" "returns the mud's connection greeting."); PyMud_addMethod("log_string", mud_log_string, METH_VARARGS, "log_string(mssg)\n" "Send a message to the mud's log."); PyMud_addMethod("is_race", mud_is_race, METH_VARARGS, "is_race(name)\n\n" "Returns True or False if the string is a valid race name."); PyMud_addMethod("list_races", mud_list_races, METH_VARARGS, "list_races(player_only=False)\n\n" "Return a list of available races. If player_only is True, list only the\n" "races that players have access to."); PyMud_addMethod("send", mud_send, METH_KEYWORDS, "send(list, mssg, dict = None, newline = True)\n" "\n" "Sends a message to a list of characters. Messages can have scripts\n" "embedded in them, using [ and ]. If so, a variable dictionary must be\n" "provided. By default, 'ch' references each character being sent the\n" "message, for embedded scripts."); PyMud_addMethod("expand_text", mud_expand_text, METH_KEYWORDS, "expand_text(text, dict={}, newline=False)\n\n" "Take text with embedded Python statements. Statements can be embedded\n" "between [ and ]. Expand them out and return the new text. Variables can\n" "be added to the scripting environment by specifying their names and\n" "values in an optional dictionary. Statements are expanded in the default\n" "scripting environment."); Py_InitModule3("mud", makePyMethods(pymud_methods), "The mud module, for all MUD misc mud utils."); globals = PyDict_New(); Py_INCREF(globals); }