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/
//*****************************************************************************
//
// room_reset.c
//
// Personally, I prefer to do all of my zone resetting through scripts because
// of the flexibility it allows for. However, I know some people are not
// too inclined towards scripting, so this is an effort to provide an easy
// way for those people to handle room resets.
//
//*****************************************************************************

#include "mud.h"
#include "utils.h"
#include "storage.h"
#include "room.h"
#include "world.h"
#include "zone.h"
#include "character.h"
#include "body.h"
#include "object.h"
#include "exit.h"
#include "handler.h"
#include "prototype.h"
#include "hooks.h"
#include "room_reset.h"



//*****************************************************************************
// mandatory modules
//*****************************************************************************
#include "items/items.h"
#include "items/container.h"
#include "items/furniture.h"
#include "items/worn.h"



//*****************************************************************************
// reset list
//*****************************************************************************
struct reset_list {
  char       *key; // our key in the world
  LIST    *resets; // our list of resets
};

RESET_LIST *newResetList(void) {
  RESET_LIST *list = malloc(sizeof(RESET_LIST));
  list->resets = newList();
  list->key    = strdup("");
  return list;
}

void deleteResetList(RESET_LIST *list) {
  if(list->resets) deleteListWith(list->resets, deleteReset);
  if(list->key)    free(list->key);
  free(list);
}

RESET_LIST *resetListCopy(RESET_LIST *list) {
  RESET_LIST *newlist = malloc(sizeof(RESET_LIST));
  if(list->resets) newlist->resets = listCopyWith(list->resets, resetCopy);
  else             newlist->resets = newList();
  newlist->key =   strdupsafe(list->key);
  return newlist;
}

void resetListCopyTo(RESET_LIST *from, RESET_LIST *to) {
  if(to->resets)   deleteListWith(to->resets, deleteReset);
  if(from->resets) to->resets = listCopyWith(from->resets, resetCopy);
  else             to->resets = newList();
  if(to->key)      free(to->key);
  to->key =        strdupsafe(from->key);
}

STORAGE_SET *resetListStore(RESET_LIST *list) {
  STORAGE_SET *set = new_storage_set();
  store_list(set, "resets", gen_store_list(list->resets, resetStore));
  return set;
}

RESET_LIST *resetListRead(STORAGE_SET *set) {
  RESET_LIST *list = calloc(1, sizeof(RESET_LIST));
  list->resets = gen_read_list(read_list(set, "resets"), resetRead);
  return list;
}

LIST *resetListGetResets(RESET_LIST *list) {
  return list->resets;
}

void resetListAdd(RESET_LIST *list, RESET_DATA *reset) {
  listPut(list->resets, reset);
}

void resetListRemove(RESET_LIST *list, RESET_DATA *reset) {
  listRemove(list->resets, reset);
}

void resetListSetKey(RESET_LIST *list, const char *key) {
  if(list->key) free(list->key);
  list->key = strdupsafe(key);
}

const char *resetListGetKey(RESET_LIST *list) {
  return list->key;
}



//*****************************************************************************
// reset data
//*****************************************************************************
struct reset_data {
  int        type; // what kind of reset are we?
  int       times; // how many times should it be executed?
  int      chance; // what is our chance of success?
  int         max; // what is the max number of us that can be in the game?
  int    room_max; // what is the max number of us that can be in the room?
  char       *arg; // what is our reset arg (e.g. mob proto, direction name)
  LIST        *in; // what resets do we put into ourself?
  LIST        *on; // what resets do we put onto ourself?
  LIST      *then; // if this succeeds, what else do we do?
};

const char *reset_names[NUM_RESETS] = {
  "load object",
  "load mobile",
  "find object",
  "find mobile",
  "purge object",
  "purge mobile",
  "open exit or object",
  "close exit or object",
  "lock exit or object",
  "change mobile position"
};


const char     *resetTypeGetName (int type) {
  return reset_names[type];
}


RESET_DATA    *newReset         () {
  RESET_DATA *reset = calloc(1, sizeof(RESET_DATA));
  reset->type     = RESET_LOAD_OBJECT;
  reset->arg      = strdup("");
  reset->times    = 1;
  reset->chance   = 100;
  reset->max      = 0;
  reset->room_max = 0;
  reset->in       = newList();
  reset->on       = newList();
  reset->then     = newList();
  return reset;
}


void           deleteReset      (RESET_DATA *reset) {
  // delete all that are attached to us
  deleteListWith(reset->in,   deleteReset);
  deleteListWith(reset->on,   deleteReset);
  deleteListWith(reset->then, deleteReset);
  if(reset->arg) free(reset->arg);
  free(reset);
}


void           resetCopyTo      (RESET_DATA *from, RESET_DATA *to) {
  // first, delete everything attached to us
  deleteListWith(to->in,   deleteReset);
  deleteListWith(to->on,   deleteReset);
  deleteListWith(to->then, deleteReset);

  to->in   = listCopyWith(from->in,   resetCopy);
  to->on   = listCopyWith(from->on,   resetCopy);
  to->then = listCopyWith(from->then, resetCopy);

  resetSetType(to,        resetGetType(from));
  resetSetTimes(to,       resetGetTimes(from));
  resetSetChance(to,      resetGetChance(from));
  resetSetMax(to,         resetGetMax(from));
  resetSetRoomMax(to,     resetGetRoomMax(from));
  resetSetArg(to,         resetGetArg(from));
}


RESET_DATA    *resetCopy        (RESET_DATA *reset) {
  RESET_DATA *newreset = newReset();
  resetCopyTo(reset, newreset);
  return newreset;
}


STORAGE_SET   *resetStore       (RESET_DATA *reset) {
  STORAGE_SET *set = new_storage_set();
  store_int   (set, "type",     reset->type);
  store_int   (set, "times",    reset->times);
  store_int   (set, "chance",   reset->chance);
  store_int   (set, "max",      reset->max);
  store_int   (set, "room_max", reset->room_max);
  store_string(set, "arg",      reset->arg);
  store_list  (set, "in",       gen_store_list(reset->in,   resetStore));
  store_list  (set, "on",       gen_store_list(reset->on,   resetStore));
  store_list  (set, "then",     gen_store_list(reset->then, resetStore));
  return set;
}


RESET_DATA    *resetRead        (STORAGE_SET *set) {
  RESET_DATA *reset = malloc(sizeof(RESET_DATA));
  reset->type     = read_int(set, "type");
  reset->times    = read_int(set, "times");
  reset->chance   = read_int(set, "chance");
  reset->max      = read_int(set, "max");
  reset->room_max = read_int(set, "room_max");
  reset->arg      = strdup(read_string(set, "arg"));
  reset->on       = gen_read_list(read_list(set, "on"),   resetRead);
  reset->in       = gen_read_list(read_list(set, "in"),   resetRead);
  reset->then     = gen_read_list(read_list(set, "then"), resetRead);
  return reset;
}


int            resetGetType     (const RESET_DATA *reset) {
  return reset->type;
}

int            resetGetTimes    (const RESET_DATA *reset) {
  return reset->times;
}

int            resetGetChance   (const RESET_DATA *reset) {
  return reset->chance;
}

int            resetGetMax      (const RESET_DATA *reset) {
  return reset->max;
}

int            resetGetRoomMax  (const RESET_DATA *reset) {
  return reset->room_max;
}

const char    *resetGetArg      (const RESET_DATA *reset) {
  return reset->arg;
}

LIST          *resetGetOn       (const RESET_DATA *reset) {
  return reset->on;
}

LIST          *resetGetIn       (const RESET_DATA *reset) {
  return reset->in;
}

LIST          *resetGetThen     (const RESET_DATA *reset) {
  return reset->then;
}

void           resetSetType     (RESET_DATA *reset, int type) {
  reset->type = type;
}

void           resetSetTimes    (RESET_DATA *reset, int times) {
  reset->times = times;
}

void           resetSetChance   (RESET_DATA *reset, int chance) {
  reset->chance = chance;
}

void           resetSetMax      (RESET_DATA *reset, int max) {
  reset->max = max;
}

void           resetSetRoomMax  (RESET_DATA *reset, int room_max) {
  reset->room_max = room_max;
}

void           resetSetArg      (RESET_DATA *reset, const char *arg) {
  if(reset->arg) free(reset->arg);
  reset->arg = strdupsafe(arg);
}

void           resetAddOn       (RESET_DATA *reset, RESET_DATA *on) {
  listPut(reset->on, on);
}

void           resetAddIn       (RESET_DATA *reset, RESET_DATA *in) {
  listPut(reset->in, in);
}

void           resetAddThen     (RESET_DATA *reset, RESET_DATA *then) {
  listPut(reset->then, then);
}



//*****************************************************************************
// resetRun and all of its related functions
//*****************************************************************************

// needs to be declared...
bool resetRun(RESET_DATA *reset, void *initiator, int initiator_type,
	      const char *locale);

//
// Perform resetRun on all the reset commands in the list, using
// initiator and initiator_type
void resetRunOn(LIST *list, void *initiator, int initiator_type, 
		const char *locale) {
  if(listSize(list) > 0) {
    LIST_ITERATOR *list_i = newListIterator(list);
    RESET_DATA     *reset = NULL;
    ITERATE_LIST(reset, list_i)
      resetRun(reset, initiator, initiator_type, locale);
    deleteListIterator(list_i);
  }
}

//
// try performing an object load, based on the reset data we have
bool try_reset_load_object(RESET_DATA *reset, void *initiator, 
			   int initiator_type, const char *locale) {
  const char *fullkey = get_fullkey_relative(resetGetArg(reset), locale);
  PROTO_DATA   *proto = worldGetType(gameworld, "oproto", fullkey);
  // if there's no prototype, break out
  if(proto == NULL || protoIsAbstract(proto))
    return FALSE;

  // see if we're already at our max
  if(resetGetMax(reset) != 0 && 
     count_objs(NULL, object_list, NULL, fullkey, FALSE) >= 
     resetGetMax(reset))
    return FALSE;
  if(initiator_type == INITIATOR_ROOM && resetGetRoomMax(reset) != 0 &&
     (count_objs(NULL, roomGetContents(initiator), NULL, fullkey,
		 FALSE) >= resetGetRoomMax(reset)))
    return FALSE;

  OBJ_DATA *obj = protoObjRun(proto);
  if(obj == NULL)
    return FALSE;

  // to the room
  if(initiator_type == INITIATOR_ROOM)
    obj_to_room(obj, initiator);
  // inside of the object
  else if(initiator_type == INITIATOR_IN_OBJ)
    obj_to_obj(obj, initiator);
  // to inventory
  else if(initiator_type == INITIATOR_IN_MOB)
    obj_to_char(obj, initiator);
  // equip the mobile
  else if(initiator_type == INITIATOR_ON_MOB) {
    // see if we can equip it
    bool equipped = (objIsType(obj, "worn") && 
		     try_equip(initiator, obj, NULL, wornGetPositions(obj)));
    // we failed! Extract the object
    if(!equipped) {
      extract_obj(obj);
      return FALSE;
    }
  }

  // we're being loaded after a mobile was loaded... let's go to the same room
  else if(initiator_type == INITIATOR_THEN_MOB)
    obj_to_room(obj, charGetRoom(initiator));
  // we're being loaded after an object was loaded... let's go wherever it went
  else if(initiator_type == INITIATOR_THEN_OBJ) {
    if(objGetRoom(initiator))
      obj_to_room(obj, objGetRoom(initiator));
    else if(objGetContainer(initiator))
      obj_to_obj(obj, objGetContainer(initiator));
    else if(objGetCarrier(initiator))
      obj_to_char(obj, objGetCarrier(initiator));
    else if(objGetWearer(initiator)) {
      bool equipped = (objIsType(obj, "worn") && 
		       try_equip(objGetWearer(initiator), obj, NULL, 
				 wornGetPositions(obj)));
      // we failed! Extract the object
      if(!equipped) {
	extract_obj(obj);
	return FALSE;
      }
    }
  }
  // hmmm... we shouldn't get this far
  else {
    extract_obj(obj);
    return FALSE;
  }

  // now, run all of our stuff
  resetRunOn(reset->on,   obj, INITIATOR_ON_OBJ,   locale);
  resetRunOn(reset->in,   obj, INITIATOR_IN_OBJ,   locale);
  resetRunOn(reset->then, obj, INITIATOR_THEN_OBJ, locale);

  return TRUE;
}


//
// try performing a mobile load, based on the reset data we have
bool try_reset_load_mobile(RESET_DATA *reset, void *initiator, 
			   int initiator_type, const char *locale) {
  const char *fullkey = get_fullkey_relative(resetGetArg(reset), locale);
  PROTO_DATA   *proto = worldGetType(gameworld, "mproto", fullkey);
  // if there's no prototype, break out
  if(proto == NULL || protoIsAbstract(proto))
    return FALSE;

  // see if we're already at our max
  if(resetGetMax(reset) != 0 && 
     count_chars(NULL, mobile_list, NULL, fullkey, FALSE) >= resetGetMax(reset))
    return FALSE;
  if(initiator_type == INITIATOR_ROOM && resetGetRoomMax(reset) != 0 &&
     (count_chars(NULL, roomGetCharacters(initiator), NULL, fullkey,
		  FALSE) >= resetGetRoomMax(reset)))
    return FALSE;

  CHAR_DATA *mob = protoMobRun(proto);
  if(mob == NULL)
    return FALSE;

  // to the room
  if(initiator_type == INITIATOR_ROOM)
    char_to_room(mob, initiator);
  // to a seat
  else if(initiator_type == INITIATOR_ON_OBJ) {
    if(!objIsType(initiator, "furniture") || objGetRoom(initiator)==NULL) {
      extract_mobile(mob);
      return FALSE;
    }
    char_to_room(mob, objGetRoom(initiator));
    char_to_furniture(mob, initiator);
    charSetPos(mob, POS_SITTING);
  }
  // after an object
  else if(initiator_type == INITIATOR_THEN_OBJ) {
    if(objGetRoom(initiator) == NULL) {
      extract_mobile(mob);
      return FALSE;
    }
    char_to_room(mob, objGetRoom(initiator));
  }
  // after another mob
  else if(initiator_type == INITIATOR_THEN_MOB)
    char_to_room(mob, charGetRoom(initiator));
  // we shouldn't get this far
  else {
    extract_mobile(mob);
    return FALSE;
  }

  // now, run all of our followup stuff
  resetRunOn(reset->on,   mob, INITIATOR_ON_MOB,   locale);
  resetRunOn(reset->in,   mob, INITIATOR_IN_MOB,   locale);
  resetRunOn(reset->then, mob, INITIATOR_THEN_MOB, locale);

  return TRUE;
}


//
// handles "find" and "purge" in one function
bool try_reset_old_object(RESET_DATA *reset, void *initiator,int initiator_type,
			  int reset_cmd, const char *locale) {
  const char *fullkey = get_fullkey_relative(resetGetArg(reset), locale);
  OBJ_DATA       *obj = NULL;

  // is it the room?
  if(initiator_type == INITIATOR_ROOM)
    obj = find_obj(NULL, roomGetContents(initiator),  1, NULL, fullkey, FALSE);
  // is it in a container?
  else if(initiator_type == INITIATOR_IN_OBJ)
    obj = find_obj(NULL, objGetContents(initiator),   1, NULL, fullkey, FALSE);
  // is it in a person's inventory?
  else if(initiator_type == INITIATOR_IN_MOB)
    obj = find_obj(NULL, charGetInventory(initiator), 1, NULL, fullkey, FALSE);
  // is it in a person's equipment?
  else if(initiator_type == INITIATOR_ON_MOB) {
    LIST *eq = bodyGetAllEq(charGetBody(initiator));
    obj = find_obj(NULL, eq, 1, NULL, fullkey, FALSE);
    deleteList(eq);
  }

  // if we didn't find it, return false
  if(obj == NULL)
    return FALSE;

  // now, run our reset sscripts
  resetRunOn(reset->on,   obj, INITIATOR_ON_OBJ,   locale);
  resetRunOn(reset->in,   obj, INITIATOR_IN_OBJ,   locale);
  resetRunOn(reset->then, obj, INITIATOR_THEN_OBJ, locale);

  // if this is a purge and it wasn't our initiator, kill it
  // if we purge our initiator, we might run into some problems
  if(obj != initiator && reset_cmd == RESET_PURGE_OBJECT)
    extract_obj(obj);
    
  return TRUE;
}


//
// find an object
bool try_reset_find_object(RESET_DATA *reset, void *initiator, 
			   int initiator_type, const char *locale) {
  return try_reset_old_object(reset, initiator, initiator_type, 
			      RESET_FIND_OBJECT, locale);
}


//
// purge an object
bool try_reset_purge_object(RESET_DATA *reset, void *initiator, 
			    int initiator_type, const char *locale) {
  return try_reset_old_object(reset, initiator, initiator_type, 
			      RESET_PURGE_OBJECT, locale);
}


//
// handles "find" and "purge" in one function
bool try_reset_old_mobile(RESET_DATA *reset, void *initiator,int initiator_type,
			  int reset_cmd, const char *locale) {
  const char *fullkey = get_fullkey_relative(resetGetArg(reset), locale);
  CHAR_DATA      *mob = NULL;

  // is it the room?
  if(initiator_type == INITIATOR_ROOM)
    mob = find_char(NULL, roomGetCharacters(initiator),1, NULL, fullkey, FALSE);
  // is it furniture?
  else if(initiator_type == INITIATOR_ON_OBJ)
    mob = find_char(NULL, objGetUsers(initiator), 1, NULL, fullkey, FALSE);
  // after an object
  else if(initiator_type == INITIATOR_THEN_OBJ) {
    if(objGetRoom(initiator) == NULL)
      return FALSE;
    mob = find_char(NULL, roomGetCharacters(objGetRoom(initiator)), 1, NULL,
		    fullkey, FALSE);
  }
  // after another mob
  else if(initiator_type == INITIATOR_THEN_MOB)
    mob = find_char(NULL, roomGetCharacters(charGetRoom(initiator)), 1, NULL,
		    fullkey, FALSE);

  // if we didn't find it, return FALSE
  if(mob == NULL)
    return FALSE;

  // if we found it, do the reset of the commands
  resetRunOn(reset->on,   mob, INITIATOR_ON_MOB,   locale);
  resetRunOn(reset->in,   mob, INITIATOR_IN_MOB,   locale);
  resetRunOn(reset->then, mob, INITIATOR_THEN_MOB, locale);

  // if this is a purge and it wasn't our initiator, kill it
  // if we purge our initiator, we might run into some problems
  if(mob != initiator && reset_cmd == RESET_PURGE_MOBILE)
    extract_mobile(mob);

  return TRUE;
}


//
// find a mobile
bool try_reset_find_mobile(RESET_DATA *reset, void *initiator, 
			   int initiator_type, const char *locale) {
  return try_reset_old_mobile(reset, initiator, initiator_type, RESET_FIND_MOBILE, locale);
}


//
// purge a mobile
bool try_reset_purge_mobile(RESET_DATA *reset, void *initiator, 
			    int initiator_type, const char *locale) {
  return try_reset_old_mobile(reset, initiator, initiator_type, RESET_PURGE_MOBILE, locale);
}


//
// try forcing a mobile to change positions
bool try_reset_position(RESET_DATA *reset, void *initiator, int initiator_type){
  if(!initiator || initiator_type != INITIATOR_THEN_MOB)
    return FALSE;
  int pos = posGetNum(resetGetArg(reset));
  if(pos < 0 || pos >= NUM_POSITIONS)
    return FALSE;
  charSetPos(initiator, pos);
  return TRUE;
}


//
// the blanket function for reset_open/close/lock
bool try_reset_opening(RESET_DATA *reset, void *initiator, int initiator_type,
		       bool closed, bool locked) {
  // we're trying to open an exit
  if(initiator_type == INITIATOR_ROOM) {
    EXIT_DATA *exit = roomGetExit(initiator, resetGetArg(reset));
    int      dirnum = DIR_NONE;

    // are we using an abbreviation?
    if(exit == NULL && (dirnum = dirGetAbbrevNum(resetGetArg(reset)))!=DIR_NONE)
      exit = roomGetExit(initiator, dirGetName(dirnum));

    // did we find a valid exit?
    if(exit == NULL)
      return FALSE;

    exitSetLocked(exit, locked);
    exitSetClosed(exit, closed);
    return TRUE;
  }

  // we're trying to open a container
  else if(initiator_type == INITIATOR_THEN_OBJ) {
    if(!objIsType(initiator, "container"))
      return FALSE;
    containerSetLocked(initiator, locked);
    containerSetClosed(initiator, closed);
    return TRUE;
  }
  // we shouldn't get this far
  else
    return FALSE;
}


//
// try opening something
bool try_reset_open(RESET_DATA *reset, void *initiator, int initiator_type) {
  return try_reset_opening(reset, initiator, initiator_type, FALSE, FALSE);
}


//
// try closing and unlocking something 
bool try_reset_close(RESET_DATA *reset, void *initiator, int initiator_type) {
  return try_reset_opening(reset, initiator, initiator_type, TRUE, FALSE);
}


//
// Try closing and locking something
bool try_reset_lock(RESET_DATA *reset, void *initiator, int initiator_type) {
  return try_reset_opening(reset, initiator, initiator_type, TRUE, TRUE);
}


//
// run the reset data
bool resetRun(RESET_DATA *reset, void *initiator, int initiator_type,
	      const char *locale) {
  //
  // possible problem: how do we know what to return if we're
  // running the reset data multiple times?
  bool ret_val = FALSE;

  // go through for however many times we need to
  int i;
  for(i = 0; i < resetGetTimes(reset); i++) {
    // If we don't make our reset chance, continue onto the next check
    if(rand_number(1, 100) > resetGetChance(reset))
      continue;
    switch(resetGetType(reset)) {
    case RESET_LOAD_OBJECT:
      ret_val = try_reset_load_object(reset, initiator, initiator_type, locale);
      break;
    case RESET_LOAD_MOBILE:
      ret_val = try_reset_load_mobile(reset, initiator, initiator_type, locale);
      break;
    case RESET_FIND_OBJECT:
      ret_val = try_reset_find_object(reset, initiator, initiator_type, locale);
      break;
    case RESET_FIND_MOBILE:
      ret_val = try_reset_find_mobile(reset, initiator, initiator_type, locale);
      break;
    case RESET_PURGE_OBJECT:
      ret_val = try_reset_purge_object(reset, initiator, initiator_type,locale);
      break;
    case RESET_PURGE_MOBILE:
      ret_val = try_reset_purge_mobile(reset, initiator, initiator_type,locale);
      break;
    case RESET_OPEN:
      ret_val = try_reset_open(reset, initiator, initiator_type);
      break;
    case RESET_CLOSE:
      ret_val = try_reset_close(reset, initiator, initiator_type);
      break;
    case RESET_LOCK:
      ret_val = try_reset_lock(reset, initiator, initiator_type);
      break;
    case RESET_POSITION:
      ret_val = try_reset_position(reset, initiator, initiator_type);
      break;
    default:
      return FALSE;
    }
  }
  return ret_val;
}



//*****************************************************************************
// initialization function
//*****************************************************************************

//
// room reset hook. Whenever a room is reset, apply all of its reset rules
void room_reset_hook(const char *info) {
  char  *zone_key = NULL;
  hookParseInfo(info, &zone_key);
  ZONE_DATA *zone = worldGetZone(gameworld, zone_key);
  free(zone_key);

  LIST_ITERATOR *res_i = newListIterator(zoneGetResettable(zone));
  char           *name = NULL;
  const char   *locale = zoneGetKey(zone);
  RESET_LIST     *list = NULL;
  ROOM_DATA      *room = NULL;
  ITERATE_LIST(name, res_i) {
    if((room = worldGetRoom(gameworld, get_fullkey(name, locale))) != NULL) {
      // first apply all of our prototype resets
      LIST            *protos = parse_keywords(roomGetPrototypes(room));
      LIST_ITERATOR  *proto_i = newListIterator(protos);
      char             *proto = NULL;

      // try to run each parent reset, and finally our own
      ITERATE_LIST(proto, proto_i) {
	if((list = worldGetType(gameworld, "reset", proto)) != NULL)
	  resetRunOn(resetListGetResets(list), room, INITIATOR_ROOM, locale);
      } deleteListIterator(proto_i);
      deleteListWith(protos, free);
    }
  } deleteListIterator(res_i);
}

void init_room_reset(void) {
  hookAdd("reset", room_reset_hook);
}