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