roh/conf.old/area/
roh/config/code/python/
roh/config/game/area/
roh/config/game/signs/
roh/help/dmhelp/
roh/help/help/
roh/log/
roh/log/staff/
roh/monsters/ocean/
roh/objects/misc/
roh/objects/ocean/
roh/player/
roh/rooms/area/1/
roh/rooms/misc/
roh/rooms/ocean/
roh/src-2.47e/
/*
 * craft.cpp
 *	 Main file for craft-related functions
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * Permission to use, modify and distribute is granted via the
 *  Creative Commons - Attribution - Non Commercial - Share Alike 3.0 License
 *    http://creativecommons.org/licenses/by-nc-sa/3.0/
 *
 * 	Copyright (C) 2007-2012 Jason Mitchell, Randi Mitchell
 * 	   Contributions by Tim Callahan, Jonathan Hseu
 *  Based on Mordor (C) Brooke Paul, Brett J. Vickers, John P. Freeman
 *
 */
#include "mud.h"
#include "craft.h"
#include "factions.h"
#include "commands.h"
#include "unique.h"
#include "math.h"

#include <sstream>
#include <iomanip>
#include <locale>
#define RECIPE_WIDTH	37

int numIngredients(Size size);


//*********************************************************************
//						findHot
//*********************************************************************

Object* findHot(const Player* player) {
	// we can cook from any hot object
	for(Object* obj : player->getConstRoomParent()->objects) {
		if(Recipe::goodObject(player, obj) && obj->flagIsSet(O_HOT))
			return(obj);
	}
	return(0);
}

//*********************************************************************
//						Recipe
//*********************************************************************

Recipe::Recipe() {
	id = minSkill = experience = 0;
	requireRecipe = sizable = false;
	skill = resultName = creator = "";
}

//*********************************************************************
//						save
//*********************************************************************

void Recipe::save(xmlNodePtr rootNode) const {
	xmlNodePtr curNode = xml::newStringChild(rootNode, "Recipe");
	xml::newNumProp(curNode, "Id", id);

	result.save(curNode, "Result", false);
	xml::saveNonNullString(curNode, "Skill", skill);
	xml::saveNonNullString(curNode, "Creator", creator);
	xml::saveNonZeroNum(curNode, "Experience", experience);
	xml::saveNonZeroNum(curNode, "RequireRecipe", requireRecipe);
	xml::saveNonZeroNum(curNode, "Sizable", sizable);

	saveList(curNode, "Ingredients", &ingredients);
	saveList(curNode, "Reusables", &reusables);
	saveList(curNode, "Equipment", &equipment);
}

//*********************************************************************
//						load
//*********************************************************************

void Recipe::load(xmlNodePtr curNode) {
	id = xml::getIntProp(curNode, "Id");
	curNode = curNode->children;
	while(curNode) {
		if(NODE_NAME(curNode, "Result")) result.load(curNode);
		else if(NODE_NAME(curNode, "MinSkill")) xml::copyToNum(minSkill, curNode);
		else if(NODE_NAME(curNode, "Experience")) xml::copyToNum(experience, curNode);
		else if(NODE_NAME(curNode, "Skill")) { xml::copyToBString(skill, curNode); }
		else if(NODE_NAME(curNode, "Creator")) { xml::copyToBString(creator, curNode); }
		else if(NODE_NAME(curNode, "Sizable")) xml::copyToBool(sizable, curNode);
		else if(NODE_NAME(curNode, "RequireRecipe")) xml::copyToBool(requireRecipe, curNode);
		else if(NODE_NAME(curNode, "Ingredients")) loadList(curNode->children, &ingredients);
		else if(NODE_NAME(curNode, "Reusables")) loadList(curNode->children, &reusables);
		else if(NODE_NAME(curNode, "Equipment")) loadList(curNode->children, &equipment);
		curNode = curNode->next;
	}
}

//*********************************************************************
//						saveList
//*********************************************************************

void Recipe::saveList(xmlNodePtr curNode, bstring name, const std::list<CatRef>* list) const {
	if(!list->size())
		return;
	xmlNodePtr childNode = xml::newStringChild(curNode, name.c_str());
	CatRef	cr;

	std::list<CatRef>::const_iterator it;
	for(it = list->begin(); it != list->end() ; it++) {
		cr = (*it);
		cr.save(childNode, "Item", false);
	}
}

//*********************************************************************
//						loadList
//*********************************************************************

void Recipe::loadList(xmlNodePtr curNode, std::list<CatRef>* list) {
	while(curNode) {
		if(NODE_NAME(curNode, "Item")) {
			CatRef cr;
			cr.load(curNode);
			list->push_back(cr);
		}
		curNode = curNode->next;
	}
}



int Recipe::getId() const { return(id); }
void Recipe::setId(int i) { id = i; }
int Recipe::getExperience() const { return(experience); }
void Recipe::setExperience(int exp) { experience = exp; }
bool Recipe::isSizable() const { return(sizable); }
void Recipe::setSizable(bool size) { sizable = size; }
CatRef Recipe::getResult() const { return(result); }
void Recipe::setResult(CatRef cr) {
	result = cr;
	resultName = "";
}
bstring Recipe::getResultName(bool appendCr) {
	Object* object=0;
	if(resultName == "" || resultName == "<unknown item>") {
		if(object || loadObject(result, &object)) {
			resultName = object->getObjStr(NULL, INV | MAG, 1);
			delete object;
		} else
			resultName = "<unknown item>";
	}
	std::ostringstream oStr;
	oStr << resultName;
	if(appendCr)
		oStr << " (" << result.rstr() << ")";
	return(oStr.str());
}
bstring Recipe::getSkill() const { return(skill); }
void Recipe::setSkill(bstring s) { skill = s; }
bstring Recipe::getCreator() const { return(creator); }
void Recipe::setCreator(bstring c) { creator = c; }
int Recipe::getMinSkill() const { return(minSkill); }
bool Recipe::requiresRecipe() const { return(requireRecipe); }
void Recipe::setRequiresRecipe(bool r) { requireRecipe = r; }

//**********************************************************************
//						isSkilled
//**********************************************************************

bool Recipe::isSkilled(const Player* player, Size recipeSize) const {
	if(skill == "")
		return(true);
	if(!player->knowsSkill(skill))
		return(false);
	int skillLvl = (int)player->getSkillGained(skill);
	// crafting recipes that are not your size is difficult
	if(isSizable()) {
		// invalid sizes cannot be crafted!
		if(recipeSize == NO_SIZE)
			return(false);
		if(recipeSize != player->getSize())
			skillLvl -= abs(player->getSize() - recipeSize) * 10;
	}
	return(skillLvl >= minSkill);
}

//**********************************************************************
//						isValid
//**********************************************************************

bool Recipe::isValid() const {
	return(result.id && !ingredients.empty());
}

//**********************************************************************
//						goodOject
//**********************************************************************
// id=0 means don't bother checking

bool Recipe::goodObject(const Player* player, const Object* object, const CatRef* cr) {
	return(	(!cr || *&object->info == *cr) &&
			player->canSee(object) &&
			(	(	object->getShotsCur() > 0 &&
					object->getShotsCur() == object->getShotsMax()
				) || (
					object->getType() == CONTAINER &&
					object->getShotsCur() == 0
			) )
	);
}

//**********************************************************************
//						check
//**********************************************************************

bool Recipe::check(const Player* player, const std::list<CatRef>* list, bstring type, int numIngredients) const {
	Object* object=0;
	const ObjectSet *set;
	if(type == "equipment")
		set = &player->getConstRoomParent()->objects;
	else
		set = &player->objects;
	ObjectSet::iterator oIt;
	std::list<CatRef>::const_iterator lIt;
	int has=0;
	if(!isSizable())
		numIngredients = 1;

	for(lIt = list->begin(); lIt != list->end() ; lIt++) {
		for( oIt = set->begin() ; oIt != set->end() ; ) {
			// do they have this ingredient?
			object = (*oIt);
			has = 0;
			if(!object->flagIsSet(O_BEING_PREPARED) && Recipe::goodObject(player, object, &(*lIt))) {
				player->printColor("You prepare %1P.\n", object);
				if(type != "equipment")
					object->setFlag(O_BEING_PREPARED);

				has++;
				while(has < numIngredients && (oIt++) != set->end() ) {
					object = (*oIt);
					if(Recipe::goodObject(player, object, &(*lIt))) {
						if(!object->flagIsSet(O_BEING_PREPARED)) {
							player->printColor("You prepare %1P.\n", object);
							if(type != "equipment")
								object->setFlag(O_BEING_PREPARED);
						}

						has++;
						if(has == numIngredients)
							break;
					}
				}
				if(has == numIngredients)
					break;
			}
			oIt++;
		}
		if(oIt == set->end()) {
			player->unprepareAllObjects();
			player->print("You lack the necessary %s listed in this recipe.\n", type.c_str());
			return(false);
		}
	}
	return(true);
}

//**********************************************************************
//						check
//**********************************************************************

bool Recipe::check(std::list<CatRef>* list, const std::list<CatRef>* require, int numIngredients) const {
	std::list<CatRef>::iterator lIt;
	std::list<CatRef>::const_iterator rIt;
	bool	found=false;
	int		has=0;

	// go through the ingredients
	for(rIt = require->begin(); rIt != require->end() ; rIt++) {
		has = 0;
		while(has < numIngredients) {
			found = false;
			for(lIt = list->begin(); lIt != list->end() ; lIt++) {
				// if the ingredient is found in the list, remove it and go on to the next
				if((*lIt) == (*rIt)) {
					list->erase(lIt);
					has++;
					found = true;
					break;
				}
			}
			if(!found)
				return(false);
		}
	}
	return(true);
}

//**********************************************************************
//						listIngredients
//**********************************************************************

bstring Recipe::listIngredients(const std::list<CatRef>* list) const {
	Object* object=0;
	std::list<CatRef>::const_iterator it;
	std::ostringstream oStr;
	int		num=1;
	CatRef lastObject;
	lastObject.id = -1;

	oStr.setf(std::ios::left, std::ios::adjustfield);
	for(it = list->begin(); it != list->end() ; it++) {

		if(lastObject.id == -1) {
			// first object: we offset by one, load and continue
			lastObject = (*it);
			continue;
		} else if(lastObject == (*it)) {
			// we have another of the same object, up the tally
			num++;
			continue;
		}

		oStr << "   |   " << std::setw(RECIPE_WIDTH);
		if(loadObject(lastObject, &object)) {
			oStr << object->getObjStr(NULL, INV | MAG, num);
			delete object;
		} else {
			oStr << "<unknown item>";
		}
		oStr << "|\n";

		lastObject = (*it);
		num = 1;
	}

	// we offset by one, so do the last one now
	oStr << "   |   " << std::setw(RECIPE_WIDTH);
	if(loadObject(lastObject, &object)) {
		oStr << object->getObjStr(NULL, INV | MAG, num);
		delete object;
	} else {
		oStr << "<unknown item>";
	}
	oStr << "|\n";

	return(oStr.str());
}


//**********************************************************************
//						display
//**********************************************************************
std::ostream& operator<<(std::ostream& out, Recipe& recipe) {
    out << recipe.display();
    return(out);
}

std::ostream& operator<<(std::ostream& out, Recipe* recipe) {
    if(recipe)
        out << *recipe;

    return(out);
}

bstring Recipe::display() {
	std::ostringstream oStr;

	if(!isValid()) {
		oStr << "Sorry, that recipe is faulty.\n";
		return oStr.str();
	}

	oStr.setf(std::ios::left, std::ios::adjustfield);
	oStr << "  _______________________________________\n"
		 << " /\\                                      \\\n";

	if(skill != "") {
		oStr << " \\_| ^WSkill Required:^x                      |\n"
			 << "   |   " << std::setw(35) << skill << "|\n"
			 << "   |                                      |\n"
			 << "   | ^WIngredients:^x                         |\n";
	} else {
		oStr << " \\_| ^WIngredients:^x                         |\n";
	}

	oStr << listIngredients(&ingredients);

	if(!reusables.empty()) {
		oStr << "   |                                      |\n"
			 << "   | ^WReusable Items:^x                      |\n"
			 << listIngredients(&reusables);
	}

	if(!equipment.empty() || skill == "cooking") {
		oStr << "   |                                      |\n"
			 << "   | ^WEquipment:^x                           |\n";
		if(!equipment.empty())
			oStr << listIngredients(&equipment);
		else if(skill == "cooking")
			oStr << "   |   any sufficiently hot object        |\n";
	}

	oStr << "   |                                      |\n"
		 << "   | ^WResult:^x                              |\n"
		 << "   |   " << std::setw(RECIPE_WIDTH) << getResultName() << "|\n";

	if(isSizable()) {
		oStr << "   |                                      |\n"
			 << "   | This recipe may be used to create    |\n"
			 << "   | objects of different sizes.          |\n";
	}

	oStr << "   |   ___________________________________|_\n"
		 << "    \\_/____________________________________/\n";

	return(oStr.str());
}


//**********************************************************************
//						canUseEquipment
//**********************************************************************

bool Recipe::canUseEquipment(const Player* player, bstring skill) const {
	if(!equipment.empty() || skill == "cooking") {
	    for(Monster* mons : player->getConstRoomParent()->monsters) {
            if(mons->canSee(player)) {
                if( mons->getAsConstMonster()->isEnemy(player) ||
                    !Faction::willDoBusinessWith(player, mons->getAsMonster()->getPrimeFaction())
                ) {
                    player->print("%M won't let you use any equipment in this room.", mons);
                    return(false);
                }
            }

	    }
	}
	return(true);
}

//**********************************************************************
//						canBeEdittedBy
//**********************************************************************

bool Recipe::canBeEdittedBy(const Player* player) const {
	if(player->isDm())
		return(true);
	return(player->getName() == creator.c_str());
}

//**********************************************************************
//						addRecipe
//**********************************************************************
// to be called when creating a brand new recipe, not when loading

void Config::addRecipe(Recipe* recipe) {
	std::map<int, Recipe*>::iterator rIt;
	int id = 0;

	for(rIt = recipes.begin(); rIt != recipes.end() ; rIt++) {
		id = MAX(id, (*rIt).first);
	}

	id++;
	recipe->setId(id);
	recipes[id] = recipe;
}

//**********************************************************************
//						remRecipe
//**********************************************************************

void Config::remRecipe(Recipe* recipe) {
	recipes.erase(recipe->getId());
}

//**********************************************************************
//						searchRecipes
//**********************************************************************
// items in inventory are prepared, this sees if they match any known recipe
// this function essentially lets people experiment and find new recipes

Recipe* Config::searchRecipes(const Player* player, bstring skill, Size recipeSize, int numIngredients, const Object* object) {
	std::list<CatRef> list, tList;
	std::list<CatRef>::const_iterator iIt;
	std::map<int, Recipe*>::iterator rIt;
	Object* hot=0;
	Recipe*	recipe=0;
	int		flags = player->displayFlags();
	unsigned int num=0;
	bstring	str = "";
	// passing in object means we don't want to print failures
	bool print = !object;

	if(object) {
		if(Recipe::goodObject(player, object)) {
			player->printColor("You prepare %1P.\n", object);
			list.push_back(object->info);
			tList.push_back(object->info);
		}
	} else {
		// make our life easier and just make a list of objects
		for( Object* obj : player->objects) {
			if(Recipe::goodObject(player, obj) && obj->flagIsSet(O_BEING_PREPARED)) {
				list.push_back(obj->info);
				tList.push_back(obj->info);
			}
		}
	}

	if(list.empty()) {
		if(print)
			player->print("You do not have any items prepared.\n");
		return(0);
	}

	num = list.size();

	// go through the recipes list and see which recipes match ALL the items
	for(rIt = recipes.begin(); rIt != recipes.end() ; rIt++) {
		recipe = (*rIt).second;

		// if they need a recipe object, they can't use it from this function
		if(!recipe || recipe->requiresRecipe())
			continue;
		// not the right type of recipe, don't even look at it
		if(recipe->getSkill() != skill)
			continue;
		if(!recipe->isValid() || !recipe->isSkilled(player, recipeSize))
			continue;

		// not the right amount of ingredients, don't even look at it
		if(num != (recipe->ingredients.size() + recipe->reusables.size()))
			continue;

		// if we cleared the list, we have one more hurdle to clear
		if(	recipe->check(&list, &recipe->ingredients, recipe->isSizable() ? numIngredients : 1) &&
			recipe->check(&list, &recipe->reusables, 1) &&
			list.empty()
		) {
			// check equipment
			bool hasEquipment=true;
			str = "";

			if(skill == "cooking" && recipe->equipment.empty()) {
				// we can cook from any hot object
				hot = findHot(player);
				if(hot) {
					str += "You prepare ";
					str += hot->getObjStr(NULL, flags, 1);
					str += ".\n";
				} else
					hasEquipment = false;
			} else {
				ObjectSet::iterator oIt;
				for(iIt = recipe->equipment.begin(); iIt != recipe->equipment.end() ; iIt++) {

					for( oIt = player->getConstRoomParent()->objects.begin() ; oIt != player->getConstRoomParent()->objects.end() ; ) {
						Object *obj = (*oIt);
						if(Recipe::goodObject(player, obj, &(*iIt))) {
							str += "You prepare ";
							str += obj->getObjStr(NULL, flags, 1);
							str += ".\n";
							break;
						}
						oIt++;
					}
					if(oIt == player->getConstRoomParent()->objects.end()) {
						hasEquipment = false;
						break;
					}
				}
			}

			// if they have all the items AND all the equipment, they can
			// probably use this recipe
			if(hasEquipment) {
				if(!recipe->canUseEquipment(player, skill))
					return(0);
				if(str != "")
					player->printColor("%s", str.c_str());
				return(recipe);
			}
		}

		// refill the original list
		list.clear();
		list.assign(tList.begin(), tList.end());
	}

	if(print)
		player->print("Your combination of prepared items didn't produce anything.\n");
	return(0);
}

//**********************************************************************
//						cmdRecipes
//**********************************************************************

int cmdRecipes(Player* player, cmd* cmnd) {
	Recipe* recipe=0;
	int		i=0, shown=0, truncate=0;
	std::list<int>::iterator it;
	std::ostringstream oStr;
	bstring filter = "";

	oStr.setf(std::ios::left, std::ios::adjustfield);

	// the parser doesn't understand "recipe 5" as two strings. this will force it to.
	if(cmnd->num == 1 && getFullstrText(cmnd->fullstr, 1) != "")
		cmnd->num = 2;

	if(cmnd->num > 1) {
		// findRecipe handles digit, #, and object, so we need to check for filter
		filter = getFullstrText(cmnd->fullstr, 1);
		if(filter == "<none>")
			filter = "none";
		if(	filter != "none" &&
			filter != "cooking" &&
			filter != "alchemy" &&
			filter != "tailoring" &&
			filter != "brewing" &&
			filter != "smithing"
		) {
			// it's not something we want to filter on
			bool ignore=false;
			recipe = player->findRecipe(cmnd, "", &ignore);
			if(recipe)
				oStr << recipe;
			player->printColor("%s\n", oStr.str().c_str());
			return(0);
		}
	}

	if(!player->recipes.empty())
		oStr << "Recipes you know: type ^yrecipes <skill>^x or ^yrecipes <num>^x.\n";
	oStr << "  __________________________________________________\n"
		 << " /\\                                                 \\\n";

	for(it = player->recipes.begin(); it != player->recipes.end() ; it++) {
		recipe = gConfig->getRecipe(*it);
		i++;

		if(	filter != "" &&
			!(	filter == recipe->getSkill() ||
				(filter == "none" && recipe->getSkill() == "")
			)
		)
			continue;

		shown++;

		if(truncate)
			continue;

		if(shown==1)
			oStr << " \\_| ";
		else
			oStr << "   | ";

		oStr << "^c#" << std::setw(3) << i << "^x "
			 << std::setw(31) << recipe->getResultName()
			 << std::setw(14) << (recipe->getSkill() == "" ? "none" : recipe->getSkill())
			 << "|\n";

		// don't spam them if they have too many recipes
		// if they filter, we must show them all
		if(shown > 80 && filter == "")
			truncate = shown;
	}

	if(truncate) {
		oStr.setf(std::ios::right, std::ios::adjustfield);
		truncate = shown - truncate;
		oStr << "   |                                                 |\n"
			 << "   |" << std::setw(16 + (truncate==1 ? 1 : 0)) << truncate << " recipe" << (truncate != 1 ? "s" : "") << " not shown.              |\n";
	}

	if(!shown) {
		oStr << " \\_|                                                 |\n";
		if(filter == "")
			oStr << "   |             You know no recipes.                |\n";
		else
			oStr << "   |         No recipes matched that filter.         |\n";
		shown += 2;
	}

	// the list looks funny when long but not tall
	while(shown < 6) {
		oStr << "   |                                                 |\n";
		shown++;
	}

	oStr << "   |   ______________________________________________|_\n"
		 << "    \\_/_______________________________________________/\n";

	player->printColor("%s\n", oStr.str().c_str());
	return(0);
}

//**********************************************************************
//						findRecipe
//**********************************************************************

Recipe* Player::findRecipe(cmd* cmnd, bstring skill, bool* searchRecipes, Size recipeSize, int numIngredients) const {
	std::list<int>::const_iterator it;
	bstring txt = getFullstrText(cmnd->fullstr, 1);

	if(txt.getAt(0) == '#' || isdigit(txt.getAt(0))) {
		int id=0, n=0;
		if(txt.getAt(0) == '#')
			txt.Delete(0, 1);
		id = atoi(txt.c_str());

		for(it = recipes.begin(); it != recipes.end() ; it++) {
			n++;
			if(id == n)
				return(gConfig->getRecipe(*it));
		}
		print("You don't know that recipe.\n");
	} else {
		Recipe* recipe = 0;
		Object* object = this->findObject(this, cmnd, 1);
		if(object) {
			// if the object they're using is a recipe
			if(object->getRecipe()) {
				recipe = gConfig->getRecipe(object->getRecipe());
				if(recipe)
					return(recipe);
			}
			// if the object they're using is the only ingredient in the recipe
			recipe = gConfig->searchRecipes(this, skill, recipeSize, numIngredients, object);
			// the calling function needs to know we ran searchRecipes
			(*searchRecipes)=true;
			if(recipe) {
				object->setFlag(O_BEING_PREPARED);
				return(recipe);
			}
			printColor("%O is not a recipe and is not the sole ingredient in a recipe.\n", object);
			printColor("Note that some recipes may require equipment in the room.\n");
		} else {
			print("Object not found.\n");
		}
		// match object based on string
		//for(it = recipes.begin(); it != recipes.end() ; it++) {
		//}
	}
	return(0);
}

//**********************************************************************
//						dmCombine
//**********************************************************************

int dmCombine(Player* player, cmd* cmnd) {
	Recipe *recipe=0;
	std::list<CatRef> objects;
	std::list<CatRef>* list;
	bstring txt = "";

	if(!player->canBuildObjects())
		return(cmdNoAuth(player));

	if(cmnd->num < 2) {
		player->print("Type *combine <list> [new | #]\n");
		player->printColor("   Acceptable list types are ^yingredients^x, ^yreusables^x, and ^yequipment^x.\n");
		player->printColor("   Don't forget to use ^y*recsave^x to save your work.\n");
		return(0);
	} else if(cmnd->num == 2) {
		recipe = gConfig->getRecipe(cmnd->val[1]);
		if(!recipe->canBeEdittedBy(player)) {
			player->print("Sorry, you are not allowed to edit that recipe.\n");
			return(0);
		}
	} else {
		recipe = new Recipe;
		recipe->setCreator(player->getName());
		gConfig->addRecipe(recipe);
		player->printColor("\nNew recipe ^y#%d^x created.\n\n", recipe->getId());
	}

	if(!strncmp(cmnd->str[1], "ingredients", strlen(cmnd->str[1]))) {
		list = &recipe->ingredients;
		txt = "ingredients";
	} else if(!strncmp(cmnd->str[1], "reusables", strlen(cmnd->str[1]))) {
		list = &recipe->reusables;
		txt = "reusables";
	} else if(!strncmp(cmnd->str[1], "equipment", strlen(cmnd->str[1]))) {
		list = &recipe->equipment;
		txt = "equipment";
	} else {
		player->print("That's not a valid list type.\n");
		return(0);
	}

	for(Object* obj : player->objects) {
		if(!obj->info.id)
			player->printColor("Skipping %P, no index found.\n", obj);
		else if(obj->flagIsSet(O_BEING_PREPARED)) {
			if(!Recipe::goodObject(player, obj))
				player->printColor("Skipping %P, it failed goodObject check.\n", obj);
			else
				objects.push_back(obj->info);
		}
	}

	// supply the new list
	list->clear();
	list->assign(objects.begin(), objects.end());

	player->print("Recipe #%d %s %s.\n", recipe->getId(), txt.c_str(),
		list->size() ? "set" : "cleared");
	player->unprepareAllObjects();
	return(0);
}

//**********************************************************************
//						dmSetRecipe
//**********************************************************************

int dmSetRecipe(Player* player, cmd* cmnd) {
	Recipe* recipe = gConfig->getRecipe(cmnd->val[1]);
	bstring txt = "";

	if(!player->canBuildObjects())
		return(cmdNoAuth(player));

	if(!recipe->canBeEdittedBy(player)) {
		player->print("Sorry, you are not allowed to edit that recipe.\n");
		return(0);
	}

	switch(low(cmnd->str[2][0])) {
	case 'd':
		if(strcmp(cmnd->str[3], "confirm")) {
			player->printColor("Are you sure you want to delete recipe #%d?\nType ^y*set rec %d del confirm^x to delete.\n",
				recipe->getId(), recipe->getId());
			return(0);
		}

		player->print("Recipe #%d deleted.\n", recipe->getId());
		log_immort(true, player, "%s deleted recipe #%d.\n",
			player->getCName(), recipe->getId());

		gConfig->remRecipe(recipe);
		delete recipe;

		break;
	case 'e':
		recipe->setExperience(cmnd->val[3]);

		player->print("Recipe #%d's Experience set to %d.\n", recipe->getId(), recipe->getExperience());
		log_immort(true, player, "%s set recipe #%d's %s to %d.\n",
			player->getCName(), recipe->getId(), "Experience", recipe->getExperience());
		break;
	case 'r':
		if(low(cmnd->str[2][1] == 'e' && low(cmnd->str[2][2]) == 'q')) {
			recipe->setRequiresRecipe(cmnd->val[2]);

			player->print("Recipe #%d's RequireRecipe set to %s.\n", recipe->getId(), recipe->requiresRecipe() ? "true" : "false");
			log_immort(true, player, "%s set recipe #%d's %s to %s.\n",
				player->getCName(), recipe->getId(), "RequireRecipe", recipe->requiresRecipe() ? "true" : "false");
		} else {
			if(cmnd->val[2] > 1 && cmnd->val[2] < 20000) {
				CatRef	cr;
				cr.id = cmnd->val[2];
				recipe->setResult(cr);
		} else {
				Object* object = player->findObject(player, cmnd, 3);
				if(!object) {
					player->print("Set result to what object?\n");
					return(0);
				} else if(!object->info.id) {
					player->printColor("%O does not have an index set.\n", object);
					return(0);
				} else {
					recipe->setResult(object->info);
				}
			}
			player->print("Recipe #%d's Result set to %s.\n", recipe->getId(), recipe->getResult().str().c_str());
			log_immort(true, player, "%s set recipe #%d's %s to %s.\n",
				player->getCName(), recipe->getId(), "Result", recipe->getResult().str().c_str());
		}
		break;
	case 's':
		if(cmnd->str[2][1] == 'i') {
		case 'i':
			recipe->setSizable(cmnd->val[2]);
			player->print("Recipe #%d's Sizable set to \"%s\".\n", recipe->getId(), recipe->isSizable() ? "true" : "false");
			log_immort(true, player, "%s set recipe #%d's %s to \"%s\".\n",
				player->getCName(), recipe->getId(), "Sizable", recipe->isSizable() ? "true" : "false");
		} else {
			txt = cmnd->str[3];
			if(txt != "" && !gConfig->getSkill(txt)) {
				player->print("The skill \"%s\" doesn't exist.\n", txt.c_str());
				return(0);
			}
			recipe->setSkill(txt);

			player->print("Recipe #%d's Skill set to \"%s\".\n", recipe->getId(), recipe->getSkill().c_str());
			log_immort(true, player, "%s set recipe #%d's %s to \"%s\".\n",
				player->getCName(), recipe->getId(), "Skill", recipe->getSkill().c_str());
		}
		break;
	default:
		player->print("Unknown setting for recipe.\nAcceptable parameters are:\n");
		player->printColor("    ^ydelete^x, ^yexperience^x, ^yrequire^x, ^yresult^x, ^yskill\n");
		player->printColor("    use ^y*combine^x to set ingredients, reusables, and equipment.\n");
	}
	return(0);
}

//**********************************************************************
//						dmRecipes
//**********************************************************************

int dmRecipes(Player* player, cmd* cmnd) {
	std::list<CatRef>::iterator lIt;
	Recipe* recipe=0;
	std::ostringstream oStr;
	bstring txt = getFullstrText(cmnd->fullstr, 1);

	if(!player->canBuildObjects())
		return(cmdNoAuth(player));

	oStr.setf(std::ios::left, std::ios::adjustfield);

	if(txt != "" && isdigit(txt.getAt(0))) {
		bool	i=false;

		recipe = gConfig->getRecipe(atoi(txt.c_str()));

		if(!recipe) {
			player->print("That is not a valid recipe!\n");
			return(0);
		}
		if(!recipe->canBeEdittedBy(player)) {
			player->print("You are not allowed to view that recipe.\n");
			return(0);
		}

		oStr << recipe;


		oStr << "Recipe: ^c" << recipe->getId() << "^w"
			 << "      Skill: ";
		if(recipe->getSkill() != "")
			oStr << "^c" << recipe->getSkill() << "^x";
		else
			oStr << "<none>";

		oStr << "    RequireRecipe: " << (recipe->requiresRecipe() ? "^gYes" : "^rNo") << "^x";

		if(!recipe->ingredients.empty()) {
			oStr << "\nIngredients: ";
			i = false;
			for(lIt = recipe->ingredients.begin(); lIt != recipe->ingredients.end() ; lIt++) {
				if(i)
					oStr << ", ";
				i = true;
				oStr << "^c" << (*lIt).rstr() << "^x";
			}
		}

		if(!recipe->reusables.empty()) {
			oStr << "\nReusables: ";
			i = false;
			for(lIt = recipe->reusables.begin(); lIt != recipe->reusables.end() ; lIt++) {
				if(i)
					oStr << ", ";
				i = true;
				oStr << "^c" << (*lIt).rstr() << "^x";
			}
		}

		if(!recipe->equipment.empty()) {
			oStr << "\nEquipment: ";
			i = false;
			for(lIt = recipe->equipment.begin(); lIt != recipe->equipment.end() ; lIt++) {
				if(i)
					oStr << ", ";
				i = true;
				oStr << "^c" << (*lIt).rstr() << "^x";
			}
		}

		oStr << "\nResult: ^c" << recipe->getResult().rstr() << "^x\n"
		 	 << "Creator: ^c" << recipe->getCreator().c_str() << "^x\n"
		 	 << "Experience: ^c" << recipe->getExperience() << "^x\n"
		 	 << "MinSkill: ^c" << recipe->getMinSkill() << "^x\n"
		 	 << "Sizable: " << (recipe->isSizable() ? "^gYes" : "^rNo") << "^x\n";

	} else {

		std::map<int, Recipe*>::iterator it;
		if(cmnd->num > 1)
			txt = getFullstrText(cmnd->fullstr, 1);

		oStr << "^yListing Recipes; type *recipe <num> for more detailed information.^x\n";

		if(txt == "")
			 oStr << "^y                 or *recipe <skill> to filter by skill^x\n";
		else
			 oStr << "^y        Filtering on skill: " << txt << "^x\n";

		for(it = gConfig->recipes.begin(); it != gConfig->recipes.end() ; it++) {
			recipe = (*it).second;

			if(	txt != "" &&
				!(	txt == recipe->getSkill() ||
					(txt == "none" && recipe->getSkill() == "")
				)
			)
				continue;
			if(!recipe->canBeEdittedBy(player))
				continue;

			oStr << "   Recipe: ^c" << std::setw(4) << recipe->getId() << "^w"
				 << " Result: ^c" << std::setw(50) << recipe->getResultName(true) << "^xSkill: ";
			if(recipe->getSkill() != "")
				oStr << "^c" << recipe->getSkill() << "^x";
			else
				oStr << "<none>";
			oStr << "\n";
		}

	}
	player->printColor("%s\n", oStr.str().c_str());
	return(0);
}


//**********************************************************************
//						learnRecipe
//**********************************************************************

void Player::learnRecipe(int id) {
	if(knowsRecipe(id))
		return;
	recipes.push_back(id);
}
void Player::learnRecipe(Recipe* recipe) {
	learnRecipe(recipe->getId());
}


//**********************************************************************
//						knowsRecipe
//**********************************************************************

bool Player::knowsRecipe(int id) const {
	std::list<int>::const_iterator it;
	for(it = recipes.begin(); it != recipes.end() ; it++) {
		if((*it) == id)
			return(true);
	}
	return(false);
}
bool Player::knowsRecipe(Recipe* recipe) const {
	return(knowsRecipe(recipe->getId()));
}

//**********************************************************************
//						loadRecipes
//**********************************************************************

bool Config::loadRecipes() {
	xmlDocPtr xmlDoc;
	xmlNodePtr curNode;
	int		i=0;

	char filename[80];
	snprintf(filename, 80, "%s/recipes.xml", Path::Game);
	xmlDoc = xml::loadFile(filename, "Recipes");

	if(xmlDoc == NULL)
		return(false);

	curNode = xmlDocGetRootElement(xmlDoc);

	curNode = curNode->children;
	while(curNode && xmlIsBlankNode(curNode))
		curNode = curNode->next;

	if(curNode == 0) {
		xmlFreeDoc(xmlDoc);
		return(false);
	}

	clearRecipes();
	while(curNode != NULL) {
		if(NODE_NAME(curNode, "Recipe")) {
			i = xml::getIntProp(curNode, "Id");

			if(recipes.find(i) == recipes.end()) {
				recipes[i] = new Recipe;
				recipes[i]->load(curNode);
			}
		}
		curNode = curNode->next;
	}
	xmlFreeDoc(xmlDoc);
	xmlCleanupParser();
	return(true);
}


//**********************************************************************
//						saveRecipes
//**********************************************************************

bool Config::saveRecipes() const {
	std::map<int, Recipe*>::const_iterator it;
	xmlDocPtr	xmlDoc;
	xmlNodePtr	rootNode;
	char			filename[80];

	xmlDoc = xmlNewDoc(BAD_CAST "1.0");
	rootNode = xmlNewDocNode(xmlDoc, NULL, BAD_CAST "Recipes", NULL);
	xmlDocSetRootElement(xmlDoc, rootNode);

	for(it = recipes.begin() ; it != recipes.end() ; it++)
		(*it).second->save(rootNode);

	sprintf(filename, "%s/recipes.xml", Path::Game);
	xml::saveFile(filename, xmlDoc);
	xmlFreeDoc(xmlDoc);
	return(true);
}

//**********************************************************************
//						clearRecipes
//**********************************************************************

void Config::clearRecipes() {
	std::map<int, Recipe*>::iterator it;

	for(it = recipes.begin() ; it != recipes.end() ; it++) {
		Recipe* r = (*it).second;
		delete r;
	}
	recipes.clear();
}


//**********************************************************************
//						getRecipe
//**********************************************************************

Recipe* Config::getRecipe(int id) {
	if(recipes.find(id) != recipes.end())
		return(recipes[id]);
	return(0);
}


//**********************************************************************
//						unprepareAllObjects
//**********************************************************************

void Player::unprepareAllObjects() const {
	for(Object* obj : objects) {
		obj->clearFlag(O_BEING_PREPARED);
	}
}

//**********************************************************************
//						removeItems
//**********************************************************************

void Player::removeItems(const std::list<CatRef>* list, int numIngredients) {
	std::list<CatRef>::const_iterator it;
	int num=0;

	for(it = list->begin(); it != list->end() ; it++) {
		num = 0;
		while(num < numIngredients) {
			for(Object *obj : objects) {
				if(Recipe::goodObject(this, obj, &(*it)) && obj->flagIsSet(O_BEING_PREPARED)) {
					delObj(obj, true, false, true, false);
					break;
				}
			}
			// updating here is safer, rather than conditionally inside the loop
			num++;
		}
	}
	checkDarkness();
}


//**********************************************************************
//						cmdPrepareObject
//**********************************************************************

int cmdPrepareObject(Player* player, cmd* cmnd) {
	Object	*object=0;

	if(cmnd->num < 2) {
		player->print("Prepare what?\n");
		return(0);
	}

	object = player->findObject(player, cmnd, 1);
	if(!object) {
		player->print("You don't have that in your inventory.\n");
		return(0);
	}

	if(object->getType() == CONTAINER && !object->objects.empty()) {
		player->printColor("You need to empty %P in order to prepare it.\n", object);
		return(0);
	}

	if(object->flagIsSet(O_BEING_PREPARED)) {
		player->print("That's already being prepared.\n");
		return(0);
	}

	player->printColor("You prepare %P.\n", object);
	broadcast(player->getSock(), player->getParent(), "%M prepares %P.", player, object);

	object->setFlag(O_BEING_PREPARED);
	return(0);
}

//**********************************************************************
//						cmdUnprepareObject
//**********************************************************************

int cmdUnprepareObject(Player* player, cmd* cmnd) {
	Object	*object=0;
	if(cmnd->num < 2) {
		player->print("Unprepare what?\n");
		return(0);
	}

	if(!strcmp(cmnd->str[1], "all")) {
		player->unprepareAllObjects();
		player->print("All prepared objects now unprepared.\n");
		return(0);
	}

	object = player->findObject(player, cmnd, 1);
	if(!object || !object->flagIsSet(O_BEING_PREPARED)) {
		player->print("You are not preparing that.\n");
		return(0);
	}

	player->printColor("You no longer are preparing %P.\n", object);
	broadcast(player->getSock(), player->getParent(), "%M stops preparing %P.", player, object);

	object->clearFlag(O_BEING_PREPARED);
	return(0);
}

void Player::checkFreeSkills(bstring skill) {
	if(	skill == "smithing" ||
		skill == "cooking" ||
		skill == "fishing" ||
		skill == "tailoring" ||
		skill == "carpentry"
	) {
		if(!knowsSkill(skill))
			addSkill(skill, 1);
	}
}

//**********************************************************************
//						cmdCraft
//**********************************************************************

int cmdCraft(Player* player, cmd* cmnd) {
	Object*	object=0;
	const Recipe* recipe=0;
	bool succeed=true, searchRecipes=false;
	std::list<int>::iterator it;
	bstring skill = "", action = cmnd->myCommand->getName(), reqSize = "";
	bstring result = "created", fail = "create";
	long t = time(0);
	Size size = player->getSize();
	int numIngredients = 1;

	player->unhide();

	// the parser doesn't understand "cook 5" as two strings. this will force it to.
	if(cmnd->num == 1 && getFullstrText(cmnd->fullstr, 1) != "")
		cmnd->num = 2;

	if(!player->isStaff()) {
		if(!player->ableToDoCommand())
			return(0);
		if(player->isBlind()) {
			player->printColor("^CYou can't do that! You're blind!\n");
			return(0);
		}
		if(player->inCombat()) {
			player->printColor("You are too busy to do that now!\n");
			return(0);
		}
		if(player->isEffected("mist")) {
			player->printColor("You must be in corporeal form to work with items.\n");
			return(0);
		}
		if(!player->canSeeRoom(player->getRoomParent(), true))
			return(0);

		if(!player->checkAttackTimer())
			return(0);
	}

	// this info will only be used for sizable recipes
	reqSize = getFullstrText(cmnd->fullstr, 2);
	if(reqSize != "") {
		size = getSize(reqSize);
		if(size == NO_SIZE)
			size = player->getSize();
		numIngredients = ::numIngredients(size);
	}

	// translate the command into an actual skill
	if(action == "smith") {
		skill = "smithing";
		result = "smithed";
		fail = "smith";
	} else if(action == "alchemy") {
		skill = "alchemy";
	} else if(action == "tailor") {
		skill = "tailoring";
		result = "tailored";
		fail = "tailor";
	} else if(action == "brew") {
		skill = "brewing";
		result = "brewed";
		fail = "brew";
	} else if(action == "cook") {
		skill = "cooking";
		result = "cooked";
		fail = "cook";
	} else if(action == "carpenter") {
		skill = "carpentry";
		result = "carpentered";
		fail = "carpenter";
	}
	// all other commands are skill-less


	// there is currently know way to train skills, so let everying be an amatuer (for now)
	player->checkFreeSkills(skill);



	// initial skill check
	if(skill != "" && !player->knowsSkill(skill)) {
		player->print("You lack the training in %s.\n", skill.c_str());
		return(0);
	}

	// using a recipe they know or a single object
	if(cmnd->num > 1) {
		// we are crafting from a recipe and need that recipe to go any further
		recipe = player->findRecipe(cmnd, skill, &searchRecipes, size, numIngredients);
		if(!recipe)
			return(0);

		if(!recipe->isSkilled(player, size)) {
			if(size != player->getSize()) {
				player->print("Sorry, you are not skilled enough in %s to use this recipe for the specified size.\n", recipe->getSkill().c_str());
			} else {
				player->print("Sorry, you are not skilled enough in %s to use this recipe.\n", recipe->getSkill().c_str());
			}
			return(0);
		}
		if(recipe->getSkill() != skill) {
			if(recipe->getSkill() == "")
				player->print("Sorry, that recipe is not a %s recipe.\n", skill.c_str());
			else
				player->print("Sorry, that recipe is a %s recipe.\n", recipe->getSkill().c_str());
			return(0);
		}

	} else {
		// we should have prepared all objects ourselves; recipe object not needed.
		// searching handles all error messages, so if bad, just exit
		if(!(recipe = gConfig->searchRecipes(player, skill, size, numIngredients)))
			return(0);
		searchRecipes = true;
	}

	if(!recipe->isValid()) {
		player->print("Sorry, that recipe is faulty.\n");
		return(0);
	}

	// searchRecipes will find everything we need - don't do it again
	if(!searchRecipes) {
		if(!recipe->canUseEquipment(player, skill))
			return(0);

		player->unprepareAllObjects();

		// checking handles all error messages, so if bad, just exit
		if(!recipe->check(player, &recipe->ingredients, "ingredients", numIngredients))
			return(0);
		if(!recipe->check(player, &recipe->reusables, "reusables", 1))
			return(0);
		if(skill == "cooking" && recipe->equipment.empty()) {
			object = findHot(player);
			if(!object) {
				player->unprepareAllObjects();
				player->print("You lack the necessary cooking equipment for this recipe.\n");
				return(0);
			} else {
				player->printColor("You prepare %1P.\n", object);
			}
		} else if(!recipe->check(player, &recipe->equipment, "equipment", 1))
			return(0);
	}


	// just check to make sure the item exists and we are set
	if(!loadObject(recipe->getResult(), &object)) {
		player->unprepareAllObjects();
		player->print("Sorry, that recipe is faulty.\n");
		return(0);
	}

	if(recipe->isSizable())
		object->setSize(size);

	if( object->getQuestnum() &&
		player->questIsSet(object->getQuestnum()-1) &&
		!player->checkStaff("You complete that recipe. You have already fulfilled that quest.\n")
	) {
		delete object;
		return(0);
	}

	if(!Lore::canHave(player, object, false)) {
		player->print("You already have enough of those.\nYou cannot currently have any more.\n");
		delete object;
		return(0);
	}
	if(!Unique::canGet(player, object)) {
		player->print("That recipe results in a limited object.\nYou cannot currently have any more.\n");
		delete object;
		return(0);
	}


	player->updateAttackTimer(true, 50);
	player->lasttime[LT_SPELL].ltime = t;
	player->lasttime[LT_SPELL].interval = 5L;
	player->lasttime[LT_MOVED].ltime = t;
	player->lasttime[LT_MOVED].misc = 5;


	player->removeItems(&recipe->ingredients, recipe->isSizable() ? numIngredients : 1);
	player->unprepareAllObjects();

	// check success: skillless recipes are always successful
	if(skill != "") {
		double chance = 30 + 20 * log(player->getSkillLevel(skill));
		if(chance < mrand(0,100))
			succeed = false;
		player->checkImprove(skill, succeed);
	}

	if(succeed) {
		player->statistics.craft();
		object->setDroppedBy(player, "Craft:"+skill);
		doGetObject(object, player);
		player->printColor("^yYou have successfully %s %1P^y.\n", result.c_str(), object);
		if(recipe && recipe->getExperience()) {
			if(!player->halftolevel()) {
				player->print("You %s %d experience.\n", gConfig->isAprilFools() ? "lose" : "gain", recipe->getExperience());
				player->addExperience(recipe->getExperience());
			}
		}
		broadcast(player->getSock(), player->getParent(), "%M successfully %s %1P.^x", player, result.c_str(), object);
	} else {
		player->printColor("^yYou attempted to %s %1P^y, but failed.\n", fail.c_str(), object);
		broadcast(player->getSock(), player->getParent(), "%M tried to %s an object.", player, fail.c_str());
		delete object;
	}

	return(0);
}