/*
* stat.cpp
* Stat code.
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* 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 "effects.h"
//#####################################################################
// Stat Modifier
//#####################################################################
StatModifier::StatModifier() {
name = "none";
modAmt = 0;
modType = MOD_NONE;
}
StatModifier::StatModifier(bstring pName, int pModAmt, ModifierType pModType) {
name = pName;
modAmt = pModAmt;
modType = pModType;
}
void StatModifier::adjust(int adjAmount) {
modAmt += adjAmount;
}
void StatModifier::set(int newAmt) {
modAmt = newAmt;
}
void StatModifier::setType(ModifierType newType) {
modType = newType;
}
bstring StatModifier::getName() {
return(name);
}
int StatModifier::getModAmt() {
return(modAmt);
}
ModifierType StatModifier:: getModType() {
return(modType);
}
double getConBonusPercentage(int pCon) {
// const double a = 0.000002102555823;
// const double b = 0.001366762953;
// const double c = 0.982621217;
const double a = 0.000007672564844;
const double b = -0.0004485369366;
const double c = 0.9939294404;
const int x = pCon;
double percentage = ((a*x*x)+(b*x)+c);
percentage = tMAX<double>(1.0, percentage)-1.0;
return(percentage);
}
double getIntBonusPercentage(int pInt) {
const double a = 0.000004207194636;
const double b = -0.0004146634469;
const double c = 1.00300606;
const int x = pInt;
double percentage = ((a*x*x)+(b*x)+c);
percentage = tMAX<double>(1.0, percentage)-1.0;
return(percentage);
}
void Stat::reCalc() {
if(!dirty)
return;
if(influencedBy) {
if(name.equals("Hp"))
setModifier("ConBonus", 0, MOD_MAX);
else if(name.equals("Mp"))
setModifier("IntBonus", 0, MOD_MAX);
}
cur = initial;
max = initial;
for(ModifierMap::value_type p : modifiers) {
StatModifier *mod = p.second;
if(!mod)
continue;
switch(mod->getModType()) {
case MOD_MAX:
max += mod->getModAmt();
break;
case MOD_CUR:
cur += mod->getModAmt();
break;
case MOD_CUR_MAX:
max += mod->getModAmt();
cur += mod->getModAmt();
break;
default:
break;
}
}
if(influencedBy && name.equals("Hp")) {
double percentage = getConBonusPercentage(influencedBy->getCur());
int rounding = this->getModifierAmt("Rounding");
int conBonus = (max-rounding) * percentage;
setModifier("ConBonus", conBonus, MOD_MAX);
max += conBonus;
}
if(influencedBy && name.equals("Mp")) {
double percentage = getIntBonusPercentage(influencedBy->getCur());
int rounding = this->getModifierAmt("Rounding");
int intBonus = (max-rounding) * percentage;
setModifier("IntBonus", intBonus, MOD_MAX);
max += intBonus;
}
if(cur > max) {
adjustModifier("CurModifier", max - cur);
cur -= (cur - max);
}
dirty = false;
}
StatModifier* Stat::getModifier(bstring name) {
ModifierMap::iterator it = modifiers.find(name);
if(it == modifiers.end())
return(NULL);
else
return(it->second);
}
int Stat::getModifierAmt(bstring name) {
StatModifier* mod = getModifier(name);
if(mod)
return(mod->getModAmt());
else
return(0);
}
Stat* Creature::getStat(bstring statName) {
if(statName == "strength") {
return(&strength);
} else if(statName == "dexterity") {
return(&dexterity);
} else if(statName == "constitution") {
return(&constitution);
} else if(statName == "intelligence") {
return(&intelligence);
} else if(statName == "piety") {
return(&piety);
} else if(statName == "hp") {
return(&hp);
} else if(statName == "mp") {
return(&mp);
} else if(statName == "focus" && isPlayer()) {
return(&getAsPlayer()->focus);
} else {
return(NULL);
}
}
bool Creature::addStatModifier(bstring statName, bstring modifierName, int modAmt, ModifierType modType) {
return(addStatModifier(statName, new StatModifier(modifierName, modAmt, modType)));
}
bool Creature::addStatModifier(bstring statName, StatModifier* statModifier) {
Stat* stat = getStat(statName);
if(!stat) {
delete(statModifier);
return(false);
}
stat->addModifier(statModifier);
if(statName.equals("constitution"))
hp.setDirty();
else if(statName.equals("intelligence"))
mp.setDirty();
return(true);
}
bool Creature::setStatDirty(bstring statName) {
Stat* stat = getStat(statName);
if(!stat)
return(false);
stat->setDirty();
return(true);
}
bool Stat::addModifier(StatModifier* toAdd) {
if(!toAdd)
return(false);
if(getModifier(toAdd->getName()) != NULL) {
std::cout << "Not adding modifer " << toAdd->getName() << std::endl;
delete toAdd;
return(false);
}
modifiers.insert(ModifierMap::value_type(toAdd->getName(), toAdd));
setDirty();
return(true);
}
void Stat::setDirty() {
dirty = true;
if(influences)
influences->setDirty();
}
bool Stat::addModifier(bstring name, int modAmt, ModifierType modType) {
if(getModifier(name) != NULL)
return(false);
return(addModifier(new StatModifier(name, modAmt, modType)));
}
bool Stat::removeModifier(bstring name) {
ModifierMap::iterator it = modifiers.find(name);
if(it == modifiers.end())
return(false);
modifiers.erase(it);
setDirty();
return(true);
}
void Stat::clearModifiers() {
ModifierMap::iterator it = modifiers.begin();
while(it != modifiers.end()) {
delete it->second;
modifiers.erase(it++);
}
}
bool Stat::adjustModifier(bstring name, int modAmt, ModifierType modType) {
StatModifier* mod = getModifier(name);
if(!mod) {
if(modAmt == 0)
return(true);
mod = new StatModifier(name, 0, modType);
modifiers.insert(ModifierMap::value_type(name, mod));
}
mod->adjust(modAmt);
if(mod->getModAmt() == 0) {
return(removeModifier(name));
}
else
mod->setType(modType);
setDirty();
return(true);
}
bool Stat::setModifier(bstring name, int newAmt, ModifierType modType) {
StatModifier* mod = getModifier(name);
if(newAmt == 0) {
if(!mod) {
return(true);
}
else {
return(removeModifier(name));
}
}
if(!mod) {
mod = new StatModifier(name, 0, modType);
modifiers.insert(ModifierMap::value_type(name, mod));
}
mod->set(newAmt);
mod->setType(modType);
setDirty();
return(true);
}
//*********************************************************************
// Stat
//*********************************************************************
Stat::Stat() {
cur = max = initial = 0;
dirty = true;
influences = influencedBy = 0;
}
StatModifier::StatModifier(StatModifier &sm) {
name = sm.name;
modAmt = sm.modAmt;
modType = sm.modType;
}
Stat& Stat::operator=(const Stat& st) {
doCopy(st);
return(*this);
}
void Stat::doCopy(const Stat& st) {
StatModifier* mod = 0;
for(ModifierMap::value_type p : st.modifiers) {
mod = new StatModifier();
(*mod) = (*p.second);
modifiers.insert(ModifierMap::value_type(mod->getName(), mod));
}
name = st.name;
cur = st.cur;
max = st.max;
initial = st.initial;
dirty = st.dirty;
influences = 0;
influencedBy = 0;
}
Stat::~Stat() {
for(ModifierMap::value_type p : modifiers) {
delete p.second;
}
modifiers.clear();
}
void Stat::setName(bstring pName) {
name = pName;
}
//*********************************************************************
// adjust
//*********************************************************************
int Stat::adjust(int amt) {
if(amt > 0)
return(increase(amt));
else
return(-1 * decrease(amt*-1));
}
//*********************************************************************
// increase
//*********************************************************************
// Legacy for hp.increase()
int Stat::increase(int amt) {
if(amt < 0)
return(0);
int increaseAmt = MAX(0, MIN(amt, getMax() - getCur()));
adjustModifier("CurModifier", increaseAmt);
return(increaseAmt);
}
//*********************************************************************
// decrease
//*********************************************************************
// Legacy for hp.decrease()
int Stat::decrease(int amt) {
if(amt < 0)
return(0);
int decreaseAmt = MIN(amt, getCur());
adjustModifier("CurModifier", -decreaseAmt);
return(decreaseAmt);
}
//*********************************************************************
// getCur
//*********************************************************************
int Stat::getCur(bool recalc) {
if(recalc)
reCalc();
return(cur);
}
//*********************************************************************
// getMax
//*********************************************************************
int Stat::getMax() {
reCalc();
return(max);
}
//*********************************************************************
// getInitial
//*********************************************************************
int Stat::getInitial() const { return(initial); }
//*********************************************************************
// addInitial
//*********************************************************************
void Stat::addInitial(int a) { initial = MAX(1, initial + a); setDirty(); }
//*********************************************************************
// setMax
//*********************************************************************
double round(double r) {
return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}
void Stat::setMax(int newMax, bool allowZero) {
newMax = MAX(allowZero ? 0 : 1, MIN(newMax, 30000));
int dmSet = getModifierAmt("DmSet");
int rounding = getModifierAmt("Rounding");
int adjustment = 0;
bool setHp = false, setMp = false;
if(name.equals("Hp"))
setHp = true;
else if(name.equals("Mp"))
setMp = true;
if((setHp || setMp) && influencedBy) {
double percentage = 0;
int bonus = 0;
if(setHp) {
bonus = getModifierAmt("ConBonus");
percentage = getConBonusPercentage(influencedBy->getCur());
}
if(setMp) {
bonus = getModifierAmt("IntBonus");
percentage = getIntBonusPercentage(influencedBy->getCur());
}
// Target max value we want
double targetMax = newMax;
// Target max value we want before any bonus
double target = targetMax / (1.0+percentage);
// Current amount, less bonus and any set amounts
int curMax = getMax() - bonus - dmSet - rounding;
// Adjustment needed to get to the target value before bonus
adjustment = round(target) - curMax;
// Calculated max based on new adjustment, without any rounding modifier
int adjMax = (curMax + adjustment) * (1.0+percentage);
this->setModifier("DmSet", adjustment, MOD_MAX);
// Due to rounding with doubles we might miss the target by 1, this will adjust it
if(adjMax != newMax) {
setModifier("Rounding", newMax - adjMax, MOD_MAX);
} else {
setModifier("Rounding", 0, MOD_MAX);
}
} else {
int curMax = getMax() - dmSet;
adjustment = newMax - curMax;
this->setModifier("DmSet", adjustment, MOD_MAX);
}
}
//*********************************************************************
// setCur
//*********************************************************************
void Stat::setCur(int newCur) {
newCur = MIN(newCur, getMax());
int modCur = newCur - getCur();
adjustModifier("CurModifier", modCur);
}
void Stat::setInfluences(Stat* pInfluences) {
influences = pInfluences;
}
void Stat::setInfluencedBy(Stat* pInfluencedBy) {
influencedBy = pInfluencedBy;
}
std::ostream& operator<<(std::ostream& out, Stat& stat) {
out << stat.toString();
return(out);
}
bstring Stat::toString() {
std::ostringstream oStr;
oStr << "^C" << name << ": ^c" << getCur() << "/" << getMax() << "(" << getInitial() << ")\n";
int i = 1;
for(ModifierMap::value_type p : modifiers) {
StatModifier* mod = p.second;
oStr << "\t" << i++ << ") ";
oStr << "^C" << mod->getName() << "^c ";
switch(mod->getModType()) {
case MOD_CUR:
oStr << "CUR ";
break;
case MOD_MAX:
oStr << "MAX ";
break;
case MOD_CUR_MAX:
oStr << "CUR+MAX ";
break;
default:
oStr << "UNKNOWN ";
break;
}
if(mod->getModAmt() >= 0)
oStr << "+";
oStr << mod->getModAmt() << "\n";
}
return(oStr.str());
}
//*********************************************************************
// setInitial
//*********************************************************************
void Stat::setInitial(int i) { initial = i; setDirty(); }
//*********************************************************************
// restore
//*********************************************************************
int Stat::restore() {
if(cur < max)
setCur(max);
return(cur);
}
//*********************************************************************
// modifyStatTotalByEffect
//*********************************************************************
int modifyStatTotalByEffect(const Player* player, const bstring& effect) {
const EffectInfo* ef = player->getEffect(effect);
if(ef)
return(ef->getStrength());
return(0);
}
//*********************************************************************
// statsAddUp
//*********************************************************************
bool Player::statsAddUp() const {
if(isStaff())
return(true);
if(flagIsSet(P_PTESTER))
return(true);
return(true);
}
//*********************************************************************
// addStatModEffect
//*********************************************************************
bool Creature::addStatModEffect(EffectInfo* effect) {
Stat* stat=0;
Player* pThis = getAsPlayer();
bool good;
ModifierType modType = MOD_CUR_MAX;
if(effect->getName() == "strength") {
if(pThis) {
if(isEffected("berserk"))
removeEffect("berserk");
if(isEffected("dkpray"))
removeEffect("dkpray");
}
good = true;
stat = &strength;
} else if(effect->getName() == "enfeeblement") {
good = false;
stat = &strength;
} else if(effect->getName() == "haste") {
if(pThis && isEffected("frenzy"))
removeEffect("frenzy");
good = true;
stat = &dexterity;
} else if(effect->getName() == "slow") {
good = false;
stat = &dexterity;
} else if(effect->getName() == "insight") {
if(pThis && isEffected("confusion"))
pThis->removeEffect("confusion");
good = true;
stat = &intelligence;
} else if(effect->getName() == "feeblemind") {
good = false;
stat = &intelligence;
} else if(effect->getName() == "prayer") {
if(isEffected("pray"))
removeEffect("pray");
good = true;
stat = &piety;
} else if(effect->getName() == "damnation") {
good = false;
stat = &piety;
} else if(effect->getName() == "fortitude") {
good = true;
stat = &constitution;
} else if(effect->getName() == "weakness") {
good = false;
stat = &constitution;
} else if(effect->getName() == "berserk") {
good = true;
stat = &strength;
} else if(effect->getName() == "frenzy") {
good = true;
stat = &dexterity;
} else if(effect->getName() == "pray") {
good = true;
stat = &piety;
} else if(effect->getName() == "dkpray") {
good = true;
stat = &strength;
} else if(effect->getName() == "bloodsac") {
modType = MOD_MAX;
good = true;
stat = &hp;
}
else {
print("Unknown stat effect: %s\n", effect->getName().c_str());
return(false);
}
int addAmt = effect->getStrength();
if(good == false && addAmt > 0)
addAmt *= -1;
// Can't go outside the range
int statMax = MAX_STAT_NUM;
int statMin = MIN_STAT_NUM;
// Different limits for hp & mp
if(stat == &hp || stat == &mp) {
statMax = 30000;
statMin = 1;
}
addAmt = tMIN(addAmt, statMax - stat->getCur());
addAmt = tMAX(addAmt, statMin - stat->getCur());
effect->setStrength(addAmt);
stat->addModifier(effect->getName(), addAmt, modType);
if(pThis) {
pThis->computeAttackPower();
pThis->computeAC();
}
return(true);
}
//*********************************************************************
// remStatModEffect
//*********************************************************************
bool Creature::remStatModEffect(EffectInfo* effect) {
Stat* stat=0;
Player* pThis = getAsPlayer();
if(effect->getName() == "strength" || effect->getName() == "enfeeblement") {
stat = &strength;
} else if(effect->getName() == "haste" || effect->getName() == "slow") {
stat = &dexterity;
} else if(effect->getName() == "insight" || effect->getName() == "feeblemind") {
stat = &intelligence;
} else if(effect->getName() == "prayer" || effect->getName() == "damnation") {
stat = &piety;
} else if(effect->getName() == "fortitude" || effect->getName() == "weakness") {
stat = &constitution;
} else if(effect->getName() == "berserk") {
stat = &strength;
} else if(effect->getName() == "frenzy") {
stat = &dexterity;
} else if(effect->getName() == "pray") {
stat = &piety;
} else if(effect->getName() == "dkpray") {
stat = &strength;
} else if(effect->getName() == "bloodsac") {
stat = &hp;
}
else {
print("Unknown stat effect: %s\n", effect->getName().c_str());
return(false);
}
stat->removeModifier(effect->getName());
if(pThis) {
pThis->computeAttackPower();
pThis->computeAC();
pThis->setFlag(P_JUST_STAT_MOD);
}
return(true);
}
//*********************************************************************
// upgradeStats
//*********************************************************************
// Only used for upgradeStats
void Stat::upgradeSetCur(int newCur) {
cur = newCur;
}
// Note: Used for upgradeStats
void checkEffect(Creature* creature, const bstring& effName, int& stat, bool positive) {
EffectInfo* eff = creature->getEffect(effName);
if(eff) {
int str = eff->getStrength();
if(!positive)
str *= -1;
stat += str;
creature->removeEffect(eff, false);
}
}
void Player::recordLevelInfo() {
std::cout << "Recording level info for " << getName() << std::endl;
statistics.startLevelHistoryTracking();
PlayerClass *pClass = gConfig->classes[getClassString()];
LevelGain *lGain = 0;
int hpAmt = 0;
for(int l = level ; l > 1 ; l--) {
lGain = pClass->getLevelGain(l);
if(!lGain)
continue;
hpAmt = lGain->getHp();
// Track level history
statistics.setLevelInfo(l, new LevelInfo(l, lGain->getHp(), lGain->getMp(), lGain->getStat(), lGain->getSave(), time(0)));
}
}
void Player::upgradeStats() {
std::cout << "Upgrading stats for " << getName() << std::endl;
*this << "Upgrading your stats to the new format.\n";
loseRage();
loseFrenzy();
losePray();
int cStr = strength.getCur(false);
int cDex = dexterity.getCur(false);
int cCon = constitution.getCur(false);
int cInt = intelligence.getCur(false);
int cPie = piety.getCur(false);
checkEffect(this, "strength", cStr, true);
checkEffect(this, "enfeeblement", cStr, false);
checkEffect(this, "haste", cDex, true);
checkEffect(this, "slow", cDex, false);
checkEffect(this, "fortitude", cCon, true);
checkEffect(this, "weakness", cCon, false);
checkEffect(this, "insight", cInt, true);
checkEffect(this, "feeblemind", cInt, false);
checkEffect(this, "prayer", cPie, true);
checkEffect(this, "damnation", cPie, false);
PlayerClass *pClass = gConfig->classes[getClassString()];
LevelGain *lGain = 0;
int hpAmt = pClass->getBaseHp();
hp.setInitial(hpAmt);
mp.setInitial(pClass->getBaseMp());
for(int l = level ; l > 1 ; l--) {
lGain = pClass->getLevelGain(l);
if(!lGain)
continue;
bstring modName = bstring("Level") + l;
hpAmt = lGain->getHp();
hp.addModifier(modName, hpAmt, MOD_CUR_MAX );
if(cClass != BERSERKER && cClass != LICH) {
mp.addModifier(modName, lGain->getMp(), MOD_CUR_MAX );
}
int switchNum = lGain->getStat();
StatModifier* newMod = new StatModifier(modName, 10, MOD_CUR_MAX);
switch(switchNum) {
case STR:
strength.addModifier(newMod);
cStr -= 10;
break;
case DEX:
dexterity.addModifier(newMod);
cDex -= 10;
break;
case CON:
constitution.addModifier(newMod);
cCon -= 10;
break;
case INT:
intelligence.addModifier(newMod);
cInt -= 10;
break;
case PTY:
piety.addModifier(newMod);
cPie -= 10;
break;
}
}
if(cClass == FIGHTER && !cClass2 && flagIsSet(P_PTESTER)) {
focus.setInitial(100);
focus.clearModifiers();
focus.addModifier("UnFocused", -100, MOD_CUR);
mp.setInitial(0);
mp.clearModifiers();
}
strength.setInitial(cStr);
dexterity.setInitial(cDex);
constitution.setInitial(cCon);
intelligence.setInitial(cInt);
piety.setInitial(cPie);
//
// std::cout << "Str: O: " << cStr << " N: " << strength.getCur() << "\n";
// std::cout << "Dex: O: " << cDex << " N: " << dexterity.getCur() << "\n";
// std::cout << "Con: O: " << cCon << " N: " << constitution.getCur() << "\n";
// std::cout << "Int: O: " << cInt << " N: " << intelligence.getCur() << "\n";
// std::cout << "Pie: O: " << cPie << " N: " << piety.getCur() << "\n";
}
void Monster::upgradeStats() {
std::cout << "Upgrading stats for " << getName() << std::endl;
*this << "Upgrading your stats to the new format.\n";
int cStr = strength.getCur(false);
int cDex = dexterity.getCur(false);
int cCon = constitution.getCur(false);
int cInt = intelligence.getCur(false);
int cPie = piety.getCur(false);
int cHp = hp.getCur(false);
int cMp = mp.getCur(false);
checkEffect(this, "strength", cStr, true);
checkEffect(this, "enfeeblement", cStr, false);
checkEffect(this, "haste", cDex, true);
checkEffect(this, "slow", cDex, false);
checkEffect(this, "fortitude", cCon, true);
checkEffect(this, "weakness", cCon, false);
checkEffect(this, "insight", cInt, true);
checkEffect(this, "feeblemind", cInt, false);
checkEffect(this, "prayer", cPie, true);
checkEffect(this, "damnation", cPie, false);
hp.setInitial(cHp);
mp.setInitial(cMp);
strength.setInitial(cStr);
dexterity.setInitial(cDex);
constitution.setInitial(cCon);
intelligence.setInitial(cInt);
piety.setInitial(cPie);
}