nakedmudv3.3/
nakedmudv3.3/lib/
nakedmudv3.3/lib/logs/
nakedmudv3.3/lib/misc/
nakedmudv3.3/lib/players/
nakedmudv3.3/lib/txt/
nakedmudv3.3/lib/world/
nakedmudv3.3/lib/world/examples/
nakedmudv3.3/lib/world/examples/mproto/
nakedmudv3.3/lib/world/examples/oproto/
nakedmudv3.3/lib/world/examples/reset/
nakedmudv3.3/lib/world/examples/rproto/
nakedmudv3.3/lib/world/examples/trigger/
nakedmudv3.3/lib/world/limbo/
nakedmudv3.3/lib/world/limbo/room/
nakedmudv3.3/lib/world/limbo/rproto/
nakedmudv3.3/src/alias/
nakedmudv3.3/src/char_vars/
nakedmudv3.3/src/editor/
nakedmudv3.3/src/example_module/
nakedmudv3.3/src/help/
nakedmudv3.3/src/set_val/
nakedmudv3.3/src/socials/
nakedmudv3.3/src/time/
//*****************************************************************************
//
// scripts.c
//
// NakedMud makes extensive use of scripting. It uses scripting to generate
// objects, mobiles, and rooms when they are loaded into the game. There are
// also scripting hooks for these things (commonly referred to as triggers), 
// which allow them to be a bit more dynamic and flavorful in the game. For
// instance, greetings when someone enters a room, repsonses to questions, 
// actions when items are received.. you know... that sort of stuff. 
//
//*****************************************************************************

#include "../mud.h"
#include "../utils.h"
#include "../world.h"
#include "../character.h"
#include "../room.h"
#include "../exit.h"
#include "../object.h"
#include "../account.h"
#include "../socket.h"
#include "../auxiliary.h"
#include "../storage.h"
#include "../handler.h"
#include "../hooks.h"

#include "scripts.h"
#include "pyplugs.h"
#include "pyaccount.h"
#include "pychar.h"
#include "pyroom.h"
#include "pyexit.h"
#include "pyobj.h"
#include "pysocket.h"
#include "pymud.h"
#include "pymudsys.h"
#include "pyhooks.h"
#include "pyevent.h"
#include "pystorage.h"
#include "pyauxiliary.h"
#include "trighooks.h"

// online editor stuff
#include "../editor/editor.h"
#include "script_editor.h"

// Python stuff
#include <compile.h>
#include <eval.h>



//*****************************************************************************
// auxiliary data
//*****************************************************************************
typedef struct {
  LIST   *triggers;
  PyObject *pyform;
} TRIGGER_AUX_DATA;

TRIGGER_AUX_DATA *newTriggerAuxData(void) {
  TRIGGER_AUX_DATA *data = malloc(sizeof(TRIGGER_AUX_DATA));
  data->triggers         = newList();
  data->pyform           = NULL;
  return data;
}

void deleteTriggerAuxData(TRIGGER_AUX_DATA *data) {
  deleteListWith(data->triggers, free);
  if(data->pyform && data->pyform->ob_refcnt > 1)
    log_string("LEAK: Memory leak (%d refcnt) on someone or something's pyform",
	       data->pyform->ob_refcnt);
  Py_XDECREF(data->pyform);
  free(data);
}

void triggerAuxDataCopyTo(TRIGGER_AUX_DATA *from, TRIGGER_AUX_DATA *to) {
  deleteListWith(to->triggers, free);
  to->triggers = listCopyWith(from->triggers, strdup);
}

TRIGGER_AUX_DATA *triggerAuxDataCopy(TRIGGER_AUX_DATA *data) {
  TRIGGER_AUX_DATA *newdata = newTriggerAuxData();
  triggerAuxDataCopyTo(data, newdata);
  return newdata;
}

char *read_one_trigger(STORAGE_SET *set) {
  return strdup(read_string(set, "trigger"));
}

TRIGGER_AUX_DATA *triggerAuxDataRead(STORAGE_SET *set) {
  TRIGGER_AUX_DATA *data = malloc(sizeof(TRIGGER_AUX_DATA));
  data->triggers = gen_read_list(read_list(set, "triggers"), read_one_trigger);
  data->pyform   = NULL;
  return data;
}

STORAGE_SET *store_one_trigger(char *key) {
  STORAGE_SET *set = new_storage_set();
  store_string(set, "trigger", key);
  return set;
}

STORAGE_SET *triggerAuxDataStore(TRIGGER_AUX_DATA *data) {
  STORAGE_SET *set = new_storage_set();
  store_list(set, "triggers", gen_store_list(data->triggers,store_one_trigger));
  return set;
}



//*****************************************************************************
// local datastructures, functions, and defines
//*****************************************************************************

//
// a stack that keeps track of the locale scripts are running in
LIST *locale_stack = NULL;

//
// Many thanks to Xanthros who pointed out that, if two scripts trigger
// eachother, we could get tossed into an infinite loop. He suggested a
// check for loop depth before each script is run. If we are deeper than
// some maximum depth, do not run the script.
#define MAX_LOOP_DEPTH   30
int script_loop_depth   = 0;

// a local variable used for storing whether or not the last script ran fine
bool script_ok = TRUE;

void expand_char_dynamic_descs(const char *info) {
  CHAR_DATA *me = NULL;
  CHAR_DATA *ch = NULL;
  hookParseInfo(info, &me, &ch);

  // if we're an NPC, do some special work for displaying us. We don't do 
  // dynamic descs for PCs because they will probably be describing themselves,
  // and we don't want to give them access to the scripting language.
  if(charIsNPC(me)) {
    PyObject *pyme = charGetPyForm(me);
    char   *locale = strdup(get_key_locale(charGetClass(me))); 
    expand_dynamic_descs(charGetLookBuffer(ch), pyme, ch, locale);
    Py_DECREF(pyme);
    free(locale);
  }
}

void  expand_obj_dynamic_descs(const char *info) {
  OBJ_DATA  *me = NULL;
  CHAR_DATA *ch = NULL;
  hookParseInfo(info, &me, &ch);

  PyObject *pyme = objGetPyForm(me);
  char   *locale = strdup(get_key_locale(objGetClass(me))); 
  expand_dynamic_descs(charGetLookBuffer(ch), pyme, ch, locale);
  Py_DECREF(pyme);
  free(locale);
}

void expand_room_dynamic_descs(const char *info) {
  ROOM_DATA *me = NULL;
  CHAR_DATA *ch = NULL;
  hookParseInfo(info, &me, &ch);

  PyObject *pyme = roomGetPyForm(me);
  char   *locale = strdup(get_key_locale(roomGetClass(me))); 
  expand_dynamic_descs(charGetLookBuffer(ch), pyme, ch, locale);
  Py_DECREF(pyme);
  free(locale);
}

void expand_exit_dynamic_descs(const char *info) {
  EXIT_DATA *me = NULL;
  CHAR_DATA *ch = NULL;
  hookParseInfo(info, &me, &ch);

  PyObject *pyme = newPyExit(me);
  char   *locale = strdup(get_key_locale(roomGetClass(exitGetRoom(me)))); 
  expand_dynamic_descs(charGetLookBuffer(ch), pyme, ch, locale);
  Py_DECREF(pyme);
  free(locale);
}

void finalize_scripts(void) {
  Py_Finalize();
}

void finalize_scripts_hook(const char *info) {
  finalize_scripts();
}



//*****************************************************************************
// player commands
//*****************************************************************************
//
// displays info on a script to a person
COMMAND(cmd_tstat) {
  if(!charGetSocket(ch))
    return;
  else {
    TRIGGER_DATA *trig = 
      worldGetType(gameworld, "trigger", get_fullkey_relative(arg, 
			      get_key_locale(roomGetClass(charGetRoom(ch)))));
    if(trig == NULL)
      send_to_char(ch, "No trigger exists with that key.\r\n");
    else {
      send_to_socket(charGetSocket(ch),
		     "--------------------------------------------------------------------------------\r\n"
		     "Name         : %s\r\n"
		     "Trigger type : %s\r\n"
		     "--------------------------------------------------------------------------------\r\n",
		     triggerGetName(trig), 
		     triggerGetType(trig));
      script_display(charGetSocket(ch), triggerGetCode(trig), FALSE);
    }
  }
}


//
// attach a new trigger to the given instanced object/mobile/room
COMMAND(cmd_attach) {
  TRIGGER_DATA *trig = NULL;
  char          *key = NULL;
  void          *tgt = NULL;
  int     found_type = PARSE_NONE;

  if(!parse_args(ch, TRUE, cmd, arg, 
		 "word [to] { ch.room obj.room.inv.eq room }",
		 &key, &tgt, &found_type))
    return;

  // check to make sure our key is OK
  if((trig = worldGetType(gameworld, "trigger", 
          get_fullkey_relative(key, 
	      get_key_locale(roomGetClass(charGetRoom(ch)))))) == NULL)
    send_to_char(ch, "No trigger exists with the key, %s.\r\n", key);
  else {
    // what are we trying to attach it to?
    if(found_type == PARSE_CHAR) {
      send_to_char(ch, "Trigger %s attached to %s.\r\n", key, charGetName(tgt));
      triggerListAdd(charGetTriggers(tgt), triggerGetKey(trig));
    }
    else if(found_type == PARSE_ROOM) {
      send_to_char(ch, "Trigger %s attached to %s.\r\n", key, roomGetName(tgt));
      triggerListAdd(roomGetTriggers(tgt), triggerGetKey(trig));
    }
    else {
      send_to_char(ch, "Trigger %s attached to %s.\r\n", key, objGetName(tgt));
      triggerListAdd(objGetTriggers(tgt), triggerGetKey(trig));
    }
  }  
}


//
// detach a trigger from to the given instanced object/mobile/room
COMMAND(cmd_detach) {
  TRIGGER_DATA *trig = NULL;
  char          *key = NULL;
  void          *tgt = NULL;
  int     found_type = PARSE_NONE;

  if(!parse_args(ch, TRUE, cmd, arg, 
		 "word [from] { ch.room obj.room.inv.eq room }",
		 &key, &tgt, &found_type))
    return;

  // check to make sure our key is OK
  if((trig = worldGetType(gameworld, "trigger", 
          get_fullkey_relative(key, 
	      get_key_locale(roomGetClass(charGetRoom(ch)))))) == NULL)
    send_to_char(ch, "which trigger did you want to detach?\r\n");
  else {
    // what are we trying to detach the trigger from?
    if(found_type == PARSE_CHAR) {
      send_to_char(ch, "Trigger %s detached from %s.\r\n", key,
		   charGetName(tgt));
      triggerListRemove(charGetTriggers(tgt), triggerGetKey(trig));
    }
    else if(found_type == PARSE_ROOM) {
      send_to_char(ch, "Trigger %s detached from %s.\r\n", key,
		   roomGetName(tgt));
      triggerListRemove(roomGetTriggers(tgt), triggerGetKey(trig));
    }
    else {
      send_to_char(ch, "Trigger %s detached to %s.\r\n", key,
		   objGetName(tgt));
      triggerListRemove(objGetTriggers(tgt), triggerGetKey(trig));
    }
  }
}

const char *triggerGetListType(TRIGGER_DATA *trigger) {
  static char buf[SMALL_BUFFER];
  sprintf(buf, "%-40s %13s", triggerGetName(trigger), triggerGetType(trigger));
  return buf;
}

// this is used for the header when printing out zone trigger info
#define TRIGGER_LIST_HEADER \
"Name                                              Type"

COMMAND(cmd_tlist) {
  do_list(ch, (arg&&*arg?arg:get_key_locale(roomGetClass(charGetRoom(ch)))),
	  "trigger", TRIGGER_LIST_HEADER, triggerGetListType);
}

COMMAND(cmd_tdelete) {
  char *name = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word", &name))
    return;
  do_delete(ch, "trigger", deleteTrigger, name);
}

COMMAND(cmd_trename) {
  char *from = NULL, *to = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word word", &from, &to))
    return;
  do_rename(ch, "trigger", from, to);
}



//*****************************************************************************
// implementation of scripts.h - triggers portion in triggers.c
//*****************************************************************************
void init_scripts(void) {
  // create our locale stack
  locale_stack = newList();

  // initialize python
  Py_Initialize();

  // initialize all of our modules written in C
  init_PyMudSys();
  init_PyAuxiliary();
  init_PyEvent();
  init_PyStorage();
  init_PyAccount();
  init_PyChar();
  init_PyRoom();
  init_PyExit();
  init_PySocket();
  init_PyObj();
  init_PyMud();
  init_PyHooks();

  // initialize all of our modules written in Python
  init_pyplugs();

  // initialize the other parts to this module
  init_script_editor();
  init_trighooks();

  // so triggers can be saved to/loaded from disk
  worldAddType(gameworld, "trigger", triggerRead, triggerStore, deleteTrigger,
	       triggerSetKey);

  // deal with auxiliary data
  auxiliariesInstall("trigger_data", 
		     newAuxiliaryFuncs(AUXILIARY_TYPE_CHAR|AUXILIARY_TYPE_ROOM |
				       AUXILIARY_TYPE_OBJ|AUXILIARY_TYPE_SOCKET|
				       AUXILIARY_TYPE_ACCOUNT,
				       newTriggerAuxData,  deleteTriggerAuxData,
				       triggerAuxDataCopyTo, triggerAuxDataCopy,
				       triggerAuxDataStore,triggerAuxDataRead));

  // add in some hooks for preprocessing scripts embedded in descs
  hookAdd("preprocess_room_desc", expand_room_dynamic_descs);
  hookAdd("preprocess_char_desc", expand_char_dynamic_descs);
  hookAdd("preprocess_obj_desc",  expand_obj_dynamic_descs);
  hookAdd("preprocess_exit_desc", expand_exit_dynamic_descs);
  hookAdd("shutdown", finalize_scripts_hook);

  /*
  // add new player commands
  add_cmd("trun", NULL, cmd_scrun, POS_UNCONSCIOUS, POS_FLYING,
	  "builder", FALSE, FALSE);
  */
  extern COMMAND(cmd_tedit); // define the command
  add_cmd("attach",  NULL, cmd_attach,  POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, FALSE);
  add_cmd("detach",  NULL, cmd_detach,  POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, FALSE);
  add_cmd("tedit",   NULL, cmd_tedit,   POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, TRUE);
  add_cmd("tstat",   NULL, cmd_tstat,   POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, FALSE);
  add_cmd("tlist",   NULL, cmd_tlist,   POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, FALSE);
  add_cmd("tdelete", NULL, cmd_tdelete, POS_UNCONSCIOUS, POS_FLYING,
	  "scripter",FALSE, FALSE);
  add_cmd("trename", NULL, cmd_trename, POS_UNCONSCIOUS, POS_FLYING,
	  "scripter", FALSE, FALSE);
}

//
// makes a dictionary with all of the neccessary stuff in it, but without
// a __builtin__ module set
PyObject *mud_script_dict(void) {
  PyObject* dict = PyDict_New();

  // add the exit() function so people can terminate scripts
  PyObject *sys = PyImport_ImportModule("sys");
  if(sys != NULL) {
    PyObject *exit = PyDict_GetItemString(PyModule_GetDict(sys), "exit");
    if(exit != NULL) PyDict_SetItemString(dict, "exit", exit);
    Py_DECREF(sys);
  }
  
  // merge all of the mud module contents with our current dict
  PyObject *mudmod = PyImport_ImportModule("mud");
  PyDict_Update(dict, PyModule_GetDict(mudmod));
  Py_DECREF(mudmod);
  mudmod = PyImport_ImportModule("char");
  PyDict_Update(dict, PyModule_GetDict(mudmod));
  Py_DECREF(mudmod);
  mudmod = PyImport_ImportModule("room");
  PyDict_Update(dict, PyModule_GetDict(mudmod));
  Py_DECREF(mudmod);
  mudmod = PyImport_ImportModule("obj");
  PyDict_Update(dict, PyModule_GetDict(mudmod));
  Py_DECREF(mudmod);
  mudmod = PyImport_ImportModule("event");
  PyDict_Update(dict, PyModule_GetDict(mudmod));
  Py_DECREF(mudmod);
  mudmod = PyImport_ImportModule("random");
  PyDict_SetItemString(dict, "random", mudmod);
  Py_DECREF(mudmod);

  return dict;
}

PyObject *restricted_script_dict(void) {
  // build up our basic dictionary
  PyObject *dict = mud_script_dict();
  
  // add restricted builtin modules
  PyObject *builtins = PyImport_ImportModule("__restricted_builtin__");
  if(builtins != NULL) {
    PyDict_SetItemString(dict, "__builtins__", builtins);
    Py_DECREF(builtins);
  }

  return dict;
}

PyObject *unrestricted_script_dict(void) {
  PyObject *dict = mud_script_dict();

  // add builtins
  PyObject *builtins = PyImport_ImportModule("__builtin__");
  if(builtins != NULL) {
    PyDict_SetItemString(dict, "__builtins__", builtins);
    Py_DECREF(builtins);
  }

  return dict;
}

void run_code(PyObject *code, PyObject *dict, const char *locale) {
  if(script_loop_depth >= MAX_LOOP_DEPTH) {
    // should we flag some sort of error, here?
    //***********
    // FINISH ME
    //***********

    script_ok = FALSE;
  }
  else {
    listPush(locale_stack, strdupsafe(locale));

    // try executing the code
    script_ok = TRUE;
    script_loop_depth++;
    PyObject *retval = PyEval_EvalCode((PyCodeObject *)code, dict, dict);
    script_loop_depth--;

    // did we throw an error?
    if(retval == NULL && PyErr_Occurred() != PyExc_SystemExit)
      script_ok = FALSE;

    // garbage collection
    free(listPop(locale_stack));
    Py_XDECREF(retval);
  }
}

PyObject *run_script_forcode(PyObject *dict, const char *script, 
			     const char *locale) {
  // try compiling the code
  PyObject *retval = Py_CompileString(script, "<string>", Py_file_input);

  // try running the code
  if(retval != NULL)
    run_code(retval, dict, locale);
  
  // did we end up with an error?
  if(retval == NULL || !last_script_ok()) {
    char *tb = getPythonTraceback();
    log_string("Script terminated with an error:\r\n%s\r\n"
	       "\r\nTraceback is:\r\n%s\r\n", script, tb);
    free(tb);
  }

  // return our code object
  return retval;
}

void run_script(PyObject *dict, const char *script, const char *locale) {
  PyObject *code = run_script_forcode(dict, script, locale);
  Py_XDECREF(code);
}

PyObject *eval_script(PyObject *dict, const char *statement,const char *locale){
  listPush(locale_stack, strdupsafe(locale));
 
  // run the statement
  PyObject *retval = PyRun_String(statement, Py_eval_input, dict, dict);

  // did we encounter an error?
  if(retval == NULL) {
    char *tb = getPythonTraceback();
    log_string("eval_script terminated with an error:\r\n%s\r\n"
	       "\r\nTraceback is:\r\n%s\r\n", statement, tb);
    free(tb);
  }

  free(listPop(locale_stack));
  return retval;
}

//
// Tries to expand out a conditional statement. Returns whatever is expanded.
// Moves i to the proper place after the conditional has been expanded in the
// originating buffer.
BUFFER *expand_dynamic_conditional(BUFFER *desc,     PyObject *dict, 
				   PyObject *retval, const char *locale,
				   int *pos) {
  BUFFER *new_desc = newBuffer(bufferLength(desc)*2);
  BUFFER     *code = newBuffer(1); // code we have to evaluate
  int         j, i = *pos + 1;     // +1 to skip the current closing ]
  bool       match = PyObject_IsTrue(retval);

  while(TRUE) {
    // copy over all of our contents for the new desc and increment i
    while(*(bufferString(desc)+i) &&
	  !startswith(bufferString(desc)+i, "[else]") &&
	  !startswith(bufferString(desc)+i, "[elif ") &&
	  !startswith(bufferString(desc)+i, "[/if]")) {
      if(match == TRUE)
	bprintf(new_desc, "%c", *(bufferString(desc) + i));
      i++;
    }

    // did we terminate?
    if(match == TRUE || startswith(bufferString(desc)+i, "[/if]"))
      break;
    // we haven't had a match yet. Are we trying an else or elif?
    else if(startswith(bufferString(desc)+i, "[else]")) {
      match  = TRUE;
      i     += 6;
    }
    else if(startswith(bufferString(desc)+i, "[elif ")) {
      // skip the elif and spaces
      i += 6;
      while(isspace(*(bufferString(desc)+i)))
	i++;

      // find our end
      int end = next_letter_in(bufferString(desc) + i, ']');

      // make sure we have it
      if(end == -1)
	break;

      // copy everything between start and end, and format the code
      bufferClear(code);
      for(j = 0; j < end; j++)
	bprintf(code, "%c", *(bufferString(desc) + i + j));
      bufferReplace(code, "\n", "", TRUE);

      // skip i up to and beyond the ] marker
      i = i + end + 1;
      
      // evaluate the code
      PyObject *retval = eval_script(dict, bufferString(code), locale);
      match            = retval && PyObject_IsTrue(retval);

      // did we encounter an error?
      if(retval == NULL)
	break;
    }
  }

  // skip everything up to our closing [/if]
  while(*(bufferString(desc)+i) && !startswith(bufferString(desc)+i, "[/if]"))
    i++;
  if(startswith(bufferString(desc)+i, "[/if]"))
    i += 4; // put us at the closing ], not the end of the ending if block

  // garbage collection
  deleteBuffer(code);
 
  // return our expansion and move our i
  *pos = i;
  return new_desc;
}

void expand_dynamic_descs_dict(BUFFER *desc, PyObject *dict,const char *locale){
  // make our new buffer
  BUFFER *new_desc = newBuffer(bufferLength(desc)*2);
  BUFFER     *code = newBuffer(1); // for things we have to evaluate
  bool   proc_cond = FALSE; // are we processing a conditional statement?

  // copy over all of our description that is not dynamic text
  int start, end, i, j, size = bufferLength(desc);
  for(i = 0; i < size; i++) {
    // figure out when our next dynamic desc is.
    start = next_letter_in(bufferString(desc) + i, '[');

    // no more
    if(start == -1) {
      // copy the rest and skip to the end of the buffer
      bprintf(new_desc, "%s", bufferString(desc) + i);
      i = size - 1;
    }
    // we have another desc
    else {
      // copy everything up to start
      while(start > 0) {
	bprintf(new_desc, "%c", *(bufferString(desc) + i));
	start--;
	i++;
      }

      // skip the start marker
      i++;

      // find our end
      end = next_letter_in(bufferString(desc) + i, ']');

      // make sure we have it
      if(end == -1)
	break;

      // copy everything between start and end, and format the code
      bufferClear(code);
      for(j = 0; j < end; j++)
	bprintf(code, "%c", *(bufferString(desc) + i + j));
      bufferReplace(code, "\n", "", TRUE);

      // skip i up to the end
      i = i + end;

      // are we trying to process a conditional statement?
      if(!strncasecmp(bufferString(code), "if ", 3)) {
	// strip out the leading if and whitespace
	char *code_copy = strdup(bufferString(code));
	char       *ptr = code_copy + 3;
	while(isspace(*ptr)) ptr++;
	
	// copy over the cleaned-up code, signal we're processing a conditional
	bufferClear(code);
	bufferCat(code, ptr);
	proc_cond = TRUE;

	// garbage collection
	free(code_copy);
      }

      // evaluate the code
      PyObject *retval = eval_script(dict, bufferString(code), locale);

      // did we encounter an error?
      if(retval == NULL)
	break;
      // are we evaluating a conditional or no?
      else if(proc_cond) {
	BUFFER *cond_retval =
	  expand_dynamic_conditional(desc, dict, retval, locale, &i);

	// if we have something to print, expand its embedded python
	if(*bufferString(cond_retval))
	  expand_dynamic_descs_dict(cond_retval, dict, locale);

	bufferCat(new_desc, bufferString(cond_retval));
	deleteBuffer(cond_retval);
	proc_cond = FALSE;
      }
      // append the output
      else if(PyString_Check(retval))
	bprintf(new_desc, "%s", PyString_AsString(retval));
      else if(PyInt_Check(retval))
	bprintf(new_desc, "%ld", PyInt_AsLong(retval));
      else if(PyFloat_Check(retval))
	bprintf(new_desc, "%lf", PyFloat_AsDouble(retval));
      // invalid return type...
      else if(retval != Py_None)
	log_string("dynamic desc had invalid evaluation: %s", 
		   bufferString(code));

      // oddly, freeing retval here corrupt memory. 
      // And not freeing it doesn't cause a memory leak. So bizarre...
      //   Py_XDECREF(retval);
    }
  }

  // copy over our contents
  bufferCopyTo(new_desc, desc);

  // garbage collection
  deleteBuffer(code);
  deleteBuffer(new_desc);
}

void expand_dynamic_descs(BUFFER *desc, PyObject *me, CHAR_DATA *ch, 
			  const char *locale) {
  // set up our dictionary
  PyObject *dict = restricted_script_dict();
  PyDict_SetItemString(dict, "me", me);
  PyDict_SetItemString(dict, "ch", charGetPyFormBorrowed(ch));

  // expand the dynamic description
  expand_dynamic_descs_dict(desc, dict, locale);

  // garbage collection
  Py_XDECREF(dict);
}

const char *get_script_locale(void) {
  return listHead(locale_stack);
}

bool last_script_ok(void) {
  return script_ok;
}

void format_script_buffer(BUFFER *script) {
  bufferReplace(script, "\r", "", TRUE);
}

LIST *charGetTriggers(CHAR_DATA *ch) {
  TRIGGER_AUX_DATA *data = charGetAuxiliaryData(ch, "trigger_data");
  return data->triggers;
}

LIST *objGetTriggers (OBJ_DATA  *obj) {
  TRIGGER_AUX_DATA *data = objGetAuxiliaryData(obj, "trigger_data");
  return data->triggers;
}

LIST *roomGetTriggers(ROOM_DATA *room) {
  TRIGGER_AUX_DATA *data = roomGetAuxiliaryData(room, "trigger_data");
  return data->triggers;
}

PyObject *charGetPyFormBorrowed(CHAR_DATA *ch) {
  TRIGGER_AUX_DATA *data = charGetAuxiliaryData(ch, "trigger_data");
  if(data->pyform == NULL)
    data->pyform = newPyChar(ch);
  return data->pyform;
}

PyObject *objGetPyFormBorrowed(OBJ_DATA  *obj) {
  TRIGGER_AUX_DATA *data = objGetAuxiliaryData(obj, "trigger_data");
  if(data->pyform == NULL)
    data->pyform = newPyObj(obj);
  return data->pyform;
}

PyObject *roomGetPyFormBorrowed(ROOM_DATA *room) {
  TRIGGER_AUX_DATA *data = roomGetAuxiliaryData(room, "trigger_data");
  if(data->pyform == NULL)
    data->pyform = newPyRoom(room);
  return data->pyform;
}

PyObject *accountGetPyFormBorrowed(ACCOUNT_DATA *acc) {
  TRIGGER_AUX_DATA *data = accountGetAuxiliaryData(acc, "trigger_data");
  if(data->pyform == NULL)
    data->pyform = newPyAccount(acc);
  return data->pyform;
}

PyObject *socketGetPyFormBorrowed(SOCKET_DATA *sock) {
  TRIGGER_AUX_DATA *data = socketGetAuxiliaryData(sock, "trigger_data");
  if(data->pyform == NULL)
    data->pyform = newPySocket(sock);
  return data->pyform;
}

PyObject *charGetPyForm(CHAR_DATA *ch) {
  PyObject *pyform = charGetPyFormBorrowed(ch);
  Py_INCREF(pyform);
  return pyform;
}

PyObject *objGetPyForm(OBJ_DATA *obj) {
  PyObject *pyform = objGetPyFormBorrowed(obj);
  Py_INCREF(pyform);
  return pyform; 
}

PyObject *roomGetPyForm(ROOM_DATA *room) {
  PyObject *pyform = roomGetPyFormBorrowed(room);
  Py_INCREF(pyform);
  return pyform;
}

PyObject *accountGetPyForm(ACCOUNT_DATA *acc) {
  PyObject *pyform = accountGetPyFormBorrowed(acc);
  Py_INCREF(pyform);
  return pyform;
}

PyObject *socketGetPyForm(SOCKET_DATA *sock) {
  PyObject *pyform = socketGetPyFormBorrowed(sock);
  Py_INCREF(pyform);
  return pyform;
}

PyObject *PyList_fromList(LIST *list, void *convertor) {
  PyObject *pylist = PyList_New(0);
  PyObject *(*conv_func)(void *) = convertor; 
  LIST_ITERATOR *list_i = newListIterator(list);
  void            *elem = NULL;
  ITERATE_LIST(elem, list_i) {
    PyObject *pyelem = conv_func(elem);
    PyList_Append(pylist, pyelem);
    Py_DECREF(pyelem);
  } deleteListIterator(list_i);
  return pylist;
}

void triggerListAdd(LIST *list, const char *trigger) {
  if(!listGetWith(list, trigger, strcasecmp))
    listPut(list, strdup(trigger));
}

void triggerListRemove(LIST *list, const char *trigger) {
  char *val = listRemoveWith(list, trigger, strcasecmp);
  if(val) free(val);
}


//
// statements we need to highlight when showing a script
const char *control_table[] = {
  "import",
  "return",
  "except",
  "while",
  "from",
  "elif",
  "else",
  "pass",
  "try",
  "def",
  "for",
  "if",
  "in",
  "is",
  "and",
  "or",
  "not",
  NULL
};


//
// returns which control string we found. returns -1 if none were found
int check_for_control(const char *ptr, int i) {
  int syn_i;
  for(syn_i = 0; control_table[syn_i] != NULL; syn_i++) {
    int len = strlen(control_table[syn_i]);
    // not enough characters for it to exist
    if(i - len + 1 < 0)
      continue;
    // we found it might have found it. Check to make
    // sure that we are surrounded by spaces or colons
    if(!strncasecmp(ptr+i-len+1, control_table[syn_i], len)) {
      // check the left side first
      if(!(i - len < 0 || isspace(ptr[i-len]) || ptr[i-len] == ':'))
	continue;
      //  and now the right side
      if(!(ptr+i+1 == '\0' || isspace(ptr[i+1]) || ptr[i+1] == ':'))
	continue;

      return syn_i;
    }
  }

  // didn't find any
  return -1;
}

void script_display(SOCKET_DATA *sock, const char *script, bool show_line_nums){
  const char *ptr = script;//buffer_string(sock->text_editor);
  char line[SMALL_BUFFER] = "\0";
  int  line_num = 1;
  int  line_i = 0, i = 0;
  bool in_line_comment = FALSE; // are we displaying a comment?
  bool in_digit = FALSE;        // are we displaying a digit?
  bool in_string  = FALSE;      // how about a string?
  char string_type = '"';       // what kinda string marker is it? ' or " ?
  int  syn_to_color = -1;       // if we're coloring flow control, which one?

  for(i = 0; ptr[i] != '\0'; i++) {
    // take off the color for digits
    if(in_digit && !isdigit(ptr[i])) {
      sprintf(line+line_i, "{g");
      line_i += 2;
      in_digit = FALSE;
    } // NO ELSE ... we might need to color something else

    // transfer over the character
    line[line_i] = ptr[i];

    // if the character is a #, color the comment red
    if(ptr[i] == '#') {
      sprintf(line+line_i, "{r#");
      line_i += 3;
      in_line_comment = TRUE;
    }

    // we've found a digit that we have to color in
    else if(isdigit(ptr[i]) && !in_digit && !in_line_comment && !in_string) {
      sprintf(line+line_i, "{y%c", ptr[i]);
      line_i += 3;
      in_digit = TRUE;
    }

    // if we've found a string marker, color/uncolor it
    else if((ptr[i] == '"' || ptr[i] == '\'') && !in_line_comment &&
	    // if we're already coloring a string and the marker
	    // types don't match up, then don't worry about it
	    !(in_string && string_type != ptr[i])) {

      if(in_string && ptr[i] == string_type)
	sprintf(line+line_i, "\%c{g", string_type);
      else
	sprintf(line+line_i, "{w%c", ptr[i]);
      
      line_i += 3;
      in_string = (in_string + 1) % 2;
      string_type = ptr[i];
    }

    // we've hit a new line
    else if(ptr[i] == '\n') {

	// do we need to show line numbers
	char line_num_info[20];
	if(show_line_nums)
	  sprintf(line_num_info, "{c%2d]  ", line_num);
	else
	  *line_num_info = '\0';

	line[line_i] = '\0';
	send_to_socket(sock, "%s{g%s{n\r\n", line_num_info, line);
	*line = '\0';
	line_i = 0;
	line_num++;
	in_line_comment = in_string = FALSE; // reset on newline
    }

    // checking while, for, if, else, elif, etc...
    // this is kinda tricky. We have to backtrack and check some stuff
    else if(!(in_line_comment || in_digit || in_string) &&
	    (syn_to_color = check_for_control(ptr, i)) != -1) {
      sprintf(line+line_i-strlen(control_table[syn_to_color])+1,
	      "{p%s{g", control_table[syn_to_color]);
      line_i += 5; // the two markers for the color, and one for new character
    }

    // didn't find anything of interest
    else
	line_i++;
  }

  line[line_i] = '\0';

  // send the last line
  if(*line) {
    if(show_line_nums)
      send_to_socket(sock, "{c%2d]{g  %s{n\r\n", line_num, line);
    else
      send_to_socket(sock, "%s{n\r\n", line);
  }

  // and kill any color that is leaking
  //  send_to_socket(sock, "{n");

  // we don't end in a newline, but we have code
  if(line_num != 1 && ptr[strlen(ptr)-1] != '\n')
    send_to_socket(sock, "{RBuffer does not end in newline!{n\r\n");
}