//*****************************************************************************
//
// 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);
}