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/
/* 
 * alchemy.cpp
 *   Alchmey classes, functions, and other handlers
 *   ____            _
 *  |  _ \ ___  __ _| |_ __ ___  ___ 
 *  | |_) / _ \/ _` | | '_ ` _ \/ __|
 *  |  _ <  __/ (_| | | | | | | \__ \
 *  |_| \_\___|\__,_|_|_| |_| |_|___/
 *
 * 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 "alchemy.h"

#include <sstream>
#include <iomanip>
#include <locale>

//########################################################################
//# AlchemyInfo
//########################################################################


//*********************************************************************
//						getDisplayString
//*********************************************************************

bstring AlchemyInfo::getDisplayString() {
	std::ostringstream displayStr;

	displayStr << "^W" << std::setw(25) << name << "^x - " << (positive ? "yes" : " ^rno^x") << " - " << std::setw(-15);
	
	if(action == "python")
		displayStr << "^c";

	displayStr << action << "^x";

	return(displayStr.str());
}



const bstring& AlchemyInfo::getName() const {
	return(name);
}
const bstring& AlchemyInfo::getAction() const {
	return(action);
}
const bstring& AlchemyInfo::getPythonScript() const {
	return(pythonScript);
}
const bstring& AlchemyInfo::getPotionPrefix() const {
	return(potionPrefix);
}
const bstring& AlchemyInfo::getPotionDisplayName() const {
	return(potionDisplayName);
}

long AlchemyInfo::getBaseDuration() const {
	return(baseDuration);
}
short AlchemyInfo::getBaseStrength() const {
	return(baseStrength);
}
bool AlchemyInfo::isThrowable() const {
	return(throwable);
}
bool AlchemyInfo::isPositive() const {
	return(positive);
}


//*********************************************************************
//						clearAlchemy
//*********************************************************************

bool Config::clearAlchemy() {
	for(AlchemyInfo* alcInfo : alchemy) {
		if(alcInfo)
			delete alcInfo;
	}
	alchemy.clear();
	return(true);
}


//*********************************************************************
//						getAlchemyInfo
//*********************************************************************

const AlchemyInfo *Config::getAlchemyInfo(bstring effect) const {
	for(AlchemyInfo* alcInfo : alchemy) {
		if(alcInfo && alcInfo->getName() == effect)
			return alcInfo;
	}
	return(NULL);
}

//*********************************************************************
//						Alchemy
//*********************************************************************

namespace Alchemy {
	const long MAX_ALCHEMY_DURATION = 3900;
	long getMaximumDuration() {
		return(MAX_ALCHEMY_DURATION);
	}
	bstring getEffectString(Object* obj, const bstring& effect) {
		if(!obj || effect.empty())
			return("*invalid*");
		return(obj->info.rstr() + "-" + effect);
	}

}


//########################################################################
//# AlchemyEffect
//########################################################################

AlchemyEffect::AlchemyEffect() {
        duration = strength = 0;
        quality = 100;
}


AlchemyEffect::AlchemyEffect(const AlchemyEffect &ae) {
	effect = ae.effect;
	duration = ae.duration;
	strength = ae.strength;
	quality = ae.quality;
}

//*********************************************************************
//						getEffect
//*********************************************************************

const bstring& AlchemyEffect::getEffect() const {
	return(effect);
}

//*********************************************************************
//						getDuration
//*********************************************************************

long AlchemyEffect::getDuration() const {
	return(duration);
}

//*********************************************************************
//						getStrength
//*********************************************************************

short AlchemyEffect::getStrength() const {
	return(strength);
}

//*********************************************************************
//						getQuality
//*********************************************************************

short AlchemyEffect::getQuality() const {
	return(quality);
}

//*********************************************************************
//						setDuration
//*********************************************************************

void AlchemyEffect::setDuration(const long newDuration) {
	duration = tMIN<long>(tMAX<long>(newDuration,0), Alchemy::getMaximumDuration());
}

//*********************************************************************
//						combineWith
//*********************************************************************
// Adjusts to the average of the two effects, used to form a potion

void AlchemyEffect::combineWith(const AlchemyEffect& ae) {
	quality = (int)(((float)(ae.quality + quality))/2.0);
}



//*********************************************************************
//						alchemyEffectVisible
//*********************************************************************

bool Player::alchemyEffectVisible(Object* obj, const bstring effect) {
	if(!obj || effect.empty())
		return(false);

	// Staff can see any effects
	if(isCt())
		return(true);

	// Anyone can see potion effects
	if(obj->getType() == POTION)
		return(true);

	// Potions have been handled above, if we get here needs to be an herb
	if(obj->getType() != HERB)
		return(false);

	bstring effectStr = Alchemy::getEffectString(obj, effect);


	std::cout << "EffectStr: " << effectStr << std::endl;

	return((knownAlchemyEffects.find(effectStr) != knownAlchemyEffects.end()));

}

//*********************************************************************
//						learnAlchemyEffect
//*********************************************************************

bool Player::learnAlchemyEffect(Object* obj, const bstring effect) {
	if(!obj || effect.empty())
		return(false);

	bstring effectStr = Alchemy::getEffectString(obj, effect);
	if(knownAlchemyEffects.find(effectStr) == knownAlchemyEffects.end()) {
		*this << ColorOn << "You have discovered a new alchemy effect: ^W" << obj->getName() << "^x has the effect: ^W" << effect << "^x\n" << ColorOff;
		knownAlchemyEffects[effectStr] = true;
		return(true);
	}

	return(false);
}

//*********************************************************************
//						showAlchemyEffects
//*********************************************************************
// NOTE: A null player is perfectly valid, so handle it properly

bstring Object::showAlchemyEffects(Player *player) {
	bstring toReturn;

	if(!alchemyEffects.empty() && (!player || player->isCt() || player->knowsSkill("alchemy"))) {
		// Find out how many effects to show this person
		std::ostringstream outStr;

		outStr << "Alchemy Effects:\n";
		for(std::pair<int, AlchemyEffect> p : alchemyEffects) {

			if(player && ! player->alchemyEffectVisible(this, p.second.getEffect()))
				continue;

			outStr << p.first << ") " << p.second.getEffect();
			if(!player || player->isDm()) {
				// Potions have duration/strength, herbs have quality
				if(type == POTION)
					outStr << " D: " << p.second.getDuration() << " S: " << p.second.getStrength();
				else
					outStr << " Q: " << p.second.getQuality();
			}
			outStr << "\n";
		}

		toReturn = outStr.str();

	}

	return(toReturn);
}


//*********************************************************************
//						cmdBrew
//*********************************************************************

int cmdBrew(Player* player, cmd* cmnd) {
	BaseRoom* room = player->getRoomParent();

	if(!player->knowsSkill("alchemy")) {
		player->print("You have no idea how to brew potions!\n");
		return(0);
	}

	// Keep track of the player's alchemy skill level
	int skillLevel = (int)(player->getSkillGained("alchemy"));

	// Handle recpies later, for now a herb container is the target
	if(cmnd->num < 2) {
		player->print("Well, what would you like to brew?\n");
		return(0);
	}

	Object* mortar=0;	// Our Mortar and Pestle

	// Lets find our mortar.  First check inventory
	mortar = player->findObject(player, cmnd, 1);

	// Second check the room
	if(!mortar)
		mortar = room->findObject(player, cmnd, 1);

	if(!mortar) {
		player->print("What would you like to brew the contents of?.\n");
		return(0);
	}


	if(mortar->getType() != CONTAINER || mortar->getSubType() != "mortar") {
		player->print("That isn't a mortar and pestle!\n");
		return(0);
	}

	if(mortar->objects.empty()) {
		player->print("But it's empty, what do you want to brew?\n");
		return(0);
	}

	if(mortar->getShotsCur() < 1) {
		player->print("You don't have enough herbs in there to brew anything.\n");
		return(0);
	} else if(mortar->getShotsCur() < 2 && skillLevel < 300) {
		player->print("You need at least two herbs to brew something.");
		return(0);
	}

	// Effects on the final potion
	std::map<bstring, AlchemyEffect> effects;

	if(mortar->getShotsCur() >= 2) {
		HerbMap effectCount;

		// We want to look at the first 4 herbs in the mortar and get a list of effects and how many occurrences of that
		// effect there are.  For any effect with 2 or more occurrences, it'll get added to the final potion
		// If we have no effects with 2 or more occurrences, we have a failed attempt to make a potion.
		int numHerbs = 0;
		for(Object *herb : mortar->objects) {

			for(std::pair<int, AlchemyEffect> p : herb->alchemyEffects) {

				bstring effect = p.second.getEffect();
				effectCount[effect].push_back(herb);

				if(effects.find(effect) == effects.end()) {
					effects[effect] = p.second;
				} else {
					// The effect is based on the minimum strength in the herbs
					effects[effect].combineWith(p.second);

				}

			}
			if(++numHerbs == 4)
				break;
		} // end while



		for( HerbMap::value_type effectPair : effectCount) {
			if(effectPair.second.size() > 1) {
				player->printColor("Using effect: ^Y%s^x.\n", effectPair.first.c_str());
				for(Object* herb : effectPair.second) {
					player->learnAlchemyEffect(herb, effectPair.first);
				}
			}
			else {
				effects.erase(effectPair.first);
			}
		} // end foreach
	}  // end if

	// We'll be using just one herb, so it'll be the first effect
	// To get here, we have to have 100 skill
	else if(mortar->getShotsCur() == 1) {
		Object* herb = *mortar->objects.begin();

		AlchemyEffect &ae = herb->alchemyEffects[1];
		effects[ae.getEffect()] = ae;
		player->printColor("Brewing a single effect potion: ^Y%s^x\n", ae.getEffect().c_str());
		player->learnAlchemyEffect(herb, ae.getEffect());
	}


	// If there's any positive effects then it's not a poison, default to poison
	bool isPotion = false;



	Object* potion = Object::getNewPotion();

	float alchemySkillModifier = player->getSkillGained("alchemy");

	int i = 1;
	// Copy the alchemy effects to the potion
	for(std::pair<bstring, AlchemyEffect> aep : effects) {
		AlchemyEffect eff = aep.second;
		long duration = 10;

		const AlchemyInfo* alc = gConfig->getAlchemyInfo(eff.getEffect());
		if(alc) {
			// Adjust things based on the alchemy info
			player->print("Found an alchemy Info!\n");
			duration = alc->getBaseDuration();
			duration = (long)((eff.getQuality() / 100.0) * duration);
			if(alc->isPositive())
				isPotion = true;
		}

		eff.setDuration(duration);
		player->print("Effect: %s Duration: %d\n", eff.getEffect().c_str(), eff.getDuration());
		potion->addAlchemyEffect(i++, eff);
	}

	potion->nameAlchemyPotion(isPotion);

	player->print("You have created %s!\n", potion->getCName());
	potion->setDroppedBy(player, "Craft:Alchemy");
	player->addObj(potion);
	return(0);
}

void Object::nameAlchemyPotion(bool potion) {
	std::ostringstream prefix;
	std::ostringstream suffix;

	if(potion)
		suffix << "potion of";
	else
		suffix << "poison of";
	bool valid = false;

	for(AlchemyEffectMap::value_type p : alchemyEffects) {
		const AlchemyInfo* alc = gConfig->getAlchemyInfo(p.second.getEffect());

		if(alc) {
			if(alc->potionNameHasPrefix()) {
				prefix << alc->getPotionPrefix() << " ";
			} else {
				suffix << " " << alc->getPotionDisplayName();
			}
		} else {
			suffix << " " << p.second.getEffect();
		}
		valid = true;
	}

	if(valid)
		setName(prefix.str() + suffix.str());
	else
		setName("murky potion");

}

//*********************************************************************
//						addAlchemyEffect
//*********************************************************************

bool Object::addAlchemyEffect(int num, const AlchemyEffect &ae) {
	if(num < 0)
		return false;
	alchemyEffects[num] = ae;

	return true;
}

//*********************************************************************
//						isAlchemyPotion
//*********************************************************************

bool Object::isAlchemyPotion() {
	return(type == POTION && alchemyEffects.size() > 0);
}

bool AlchemyInfo::potionNameHasPrefix() const {
	return(!potionPrefix.empty());
}

//*********************************************************************
//						consumeAlchemyPotion
//*********************************************************************

// Return: Was it consumed?
bool Object::consumeAlchemyPotion(Creature* consumer) {
	if(!isAlchemyPotion() || !consumer)
		return false;
	// TODO: Verify we're not in a no potion room

	bool consumed = false;

	for(std::pair<int, AlchemyEffect> ae : alchemyEffects) {
		AlchemyEffect &eff = ae.second;
		const AlchemyInfo* alc = gConfig->getAlchemyInfo(eff.getEffect());
		if(!alc || alc->getAction() == "effect") {
			// If one of the effects takes hold, the potion was consumed
			if(eff.apply(consumer))
				consumed = true;
		}
		else if(alc->getAction()== "python") {
			if(gServer->runPython(alc->getPythonScript(), "", consumer, this))
				consumed = true;
		}
		else
			consumer->print("Unknown action: %s\n", alc->getAction().c_str());
	}

	return(consumed);
}

//*********************************************************************
//						apply
//*********************************************************************
// Apply this effect to the creature:
// Returns: false failure, true success

bool AlchemyEffect::apply(Creature* target) {
	// TODO: Check if it's an effect to add or something to apply immediately (death, heal, etc)
	bool add = true;

	if(effect == "poison" && target->immuneToPoison())
		add = false;
	else if(effect == "disease" && target->immuneToDisease())
		add = false;

	if(add)
		return(target->addEffect(effect, duration, strength));

	return(false);
}