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