/*
* levelGain.cpp
* Level Gain
* ____ _
* | _ \ ___ __ _| |_ __ ___ ___
* | |_) / _ \/ _` | | '_ ` _ \/ __|
* | _ < __/ (_| | | | | | | \__ \
* |_| \_\___|\__,_|_|_| |_| |_|___/
*
* 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 "bank.h"
#include "web.h"
char statStr[][5] = {
"NONE", "STR", "DEX", "CON", "INT", "PTY", "CHA"
};
char saveStr[][5] = {
"LCK", "POI", "DEA", "BRE", "MEN", "SPL"
};
//*********************************************************************
// findStat
//*********************************************************************
int findStat(bstring &stat) {
for(int i = 0 ; i < 7 ; i++) {
if(stat == statStr[i]) {
return(i);
}
}
return(-1);
}
//*********************************************************************
// findSave
//*********************************************************************
int findSave(bstring &save) {
for(int i = 0 ; i < 6 ; i++) {
if(save == saveStr[i]) {
return(i);
}
}
return(-1);
}
//*********************************************************************
// LevelGain
//*********************************************************************
LevelGain::LevelGain(xmlNodePtr rootNode) {
load(rootNode);
}
LevelGain::~LevelGain() {
std::list<SkillGain*>::iterator sgIt;
SkillGain* sGain;
for(sgIt = skills.begin() ; sgIt != skills.end() ; sgIt++) {
sGain = (*sgIt);
delete sGain;
}
skills.clear();
}
//*********************************************************************
// load
//*********************************************************************
void LevelGain::load(xmlNodePtr rootNode) {
xmlNodePtr curNode = rootNode->children;
bstring temp;
while(curNode) {
if(NODE_NAME(curNode, "HpGain")) {
xml::copyToNum(hp, curNode);
} else if(NODE_NAME(curNode, "MpGain")) {
xml::copyToNum(mp, curNode);
} else if(NODE_NAME(curNode, "Stat")) {
xml::copyToBString(temp, curNode);
stat = findStat(temp);
} else if(NODE_NAME(curNode, "Save")) {
xml::copyToBString(temp, curNode);
save = findSave(temp);
} else if(NODE_NAME(curNode, "Skills")) {
xmlNodePtr skillNode = curNode->children;
while(skillNode) {
if(NODE_NAME(skillNode, "Skill")) {
SkillGain* skillGain = new SkillGain(skillNode);
skills.push_back(skillGain);
}
skillNode = skillNode->next;
}
}
curNode = curNode->next;
}
}
//*********************************************************************
// hasSkills
//*********************************************************************
bool LevelGain::hasSkills() { return(!skills.empty()); }
//*********************************************************************
// getSkillBegin
//*********************************************************************
std::list<SkillGain*>::const_iterator LevelGain::getSkillBegin() { return(skills.begin()); }
//*********************************************************************
// getSkillEnd
//*********************************************************************
std::list<SkillGain*>::const_iterator LevelGain::getSkillEnd() { return(skills.end()); }
//*********************************************************************
// getStatStr
//*********************************************************************
bstring LevelGain::getStatStr() { return(statStr[stat]); }
//*********************************************************************
// getSaveStr
//*********************************************************************
bstring LevelGain::getSaveStr() { return(saveStr[save]); }
//*********************************************************************
// getStat
//*********************************************************************
int LevelGain::getStat() { return(stat); }
//*********************************************************************
// getSave
//*********************************************************************
int LevelGain::getSave() { return(save); }
//*********************************************************************
// getHp
//*********************************************************************
int LevelGain::getHp() { return(hp); }
//*********************************************************************
// getMp
//*********************************************************************
int LevelGain::getMp() { return(mp); }
//*********************************************************************
// doTrain
//*********************************************************************
void doTrain(Player* player) {
player->upLevel();
player->setFlag(P_JUST_TRAINED);
broadcast("### %s just made a level!", player->getCName());
player->print("Congratulations, you made a level!\n\n");
if(player->canChooseCustomTitle()) {
player->setFlag(P_CAN_CHOOSE_CUSTOM_TITLE);
if(!player->flagIsSet(P_CANNOT_CHOOSE_CUSTOM_TITLE))
player->print("You may now choose a custom title. Type ""help title"" for more details.\n");
}
logn("log.train", "%s just trained to level %d in room %s.\n",
player->getCName(), player->getLevel(), player->getRoomParent()->fullName().c_str());
updateRecentActivity();
}
//*********************************************************************
// upLevel
//*********************************************************************
// This function should be called whenever a player goes up a level.
// It raises there hit points and magic points appropriately, and if
// it is initializing a new character, it sets up the character.
void Player::upLevel() {
int a=0;
bool relevel=false;
if(isStaff()) {
level++;
return;
}
PlayerClass *pClass = gConfig->classes[getClassString()];
const RaceData* rData = gConfig->getRace(race);
LevelGain *lGain = 0;
if(level == actual_level)
actual_level++;
else
relevel = true;
level++;
// Check for level info
if(!pClass) {
print("Error: Can't find your class!\n");
if(!isStaff()) {
bstring errorStr = "Error: Can't find class: " + getClassString();
merror(errorStr.c_str(), FATAL);
}
return;
} else {
print("Checking Leveling Information for [%s:%d].\n", pClass->getName().c_str(), level);
lGain = pClass->getLevelGain(level);
if(!lGain && level != 1) {
print("Error: Can't find any information for your level!\n");
if(!isStaff()) {
bstring errorStr = "Error: Can't find level info for " + getClassString() + level;
merror(errorStr.c_str(), FATAL);
}
return;
}
}
if(!relevel) {
// Only check weapon gains on a real level
checkWeaponSkillGain();
}
if(cClass == FIGHTER && !cClass2 && flagIsSet(P_PTESTER)) {
focus.setInitial(100);
focus.addModifier("UnFocused", -100, MOD_CUR);
}
if(level == 1) {
statistics.startLevelHistoryTracking();
hp.setInitial(pClass->getBaseHp());
if(cClass != BERSERKER && cClass != LICH)
mp.setInitial(pClass->getBaseMp());
hp.restore();
mp.restore();
damage = pClass->damage;
if(!rData) {
print("Error: Can't find your race, no saving throws for you!\n");
} else {
saves[POI].chance = rData->getSave(POI);
saves[DEA].chance = rData->getSave(DEA);
saves[BRE].chance = rData->getSave(BRE);
saves[MEN].chance = rData->getSave(MEN);
saves[SPL].chance = rData->getSave(SPL);
setFlag(P_SAVING_THROWSPELL_LEARN);
checkSkillsGain(rData->getSkillBegin(), rData->getSkillEnd());
}
// Base Skills
if(!pClass) {
print("Error: Can't find your class, no skills for you!\n");
} else {
checkSkillsGain(pClass->getSkillBegin(), pClass->getSkillEnd());
}
} else {
bstring modName = bstring("Level") + level;
// Calculate gains here
int statGain = lGain->getStat();
int saveGain = lGain->getSave();
int hpAmt = lGain->getHp();
int mpAmt = 0;
if(cClass != BERSERKER && cClass != LICH) {
mpAmt = lGain->getMp();
}
LevelInfo* levelInfo = statistics.getLevelInfo(level);
// If we have a level info, it's a relevel so use the previous gains!
if(levelInfo) {
statGain = levelInfo->getStatUp();
saveGain = levelInfo->getSaveGain();
hpAmt = levelInfo->getHpGain();
mpAmt = levelInfo->getMpGain();
}
// Add gains here
StatModifier* newMod = new StatModifier(modName, 10, MOD_CUR_MAX);
switch(statGain) {
case STR:
addStatModifier("strength", newMod);
print("You have become stronger.\n");
break;
case DEX:
addStatModifier("dexterity",newMod);
print("You have become more dextrous.\n");
break;
case CON:
addStatModifier("constitution", newMod);
print("You have become healthier.\n");
break;
case INT:
addStatModifier("intelligence", newMod);
print("You have become more intelligent.\n");
break;
case PTY:
addStatModifier("piety", newMod);
print("You have become more pious.\n");
break;
}
addStatModifier("hp", modName, hpAmt, MOD_CUR_MAX );
if(cClass != BERSERKER && cClass != LICH) {
addStatModifier("mp", modName, mpAmt, MOD_CUR_MAX );
}
switch (saveGain) {
case POI:
saves[POI].chance += 3;
print("You are now more resistant to poison.\n");
break;
case DEA:
saves[DEA].chance += 3;
print("You are now more able to resist traps and death.\n");
break;
case BRE:
saves[BRE].chance += 3;
print("You are now more resistant to breath weapons and explosions.\n");
break;
case MEN:
saves[MEN].chance += 3;
print("You are now more resistant to mind dominating attacks.\n");
break;
case SPL:
saves[SPL].chance += 3;
print("You are now more resistant to magical spells.\n");
break;
}
if(!relevel) {
statistics.setLevelInfo(level, new LevelInfo(level, hpAmt, mpAmt, statGain, saveGain, time(0)));
// Saving throw bug fix: Spells and mental saving throws will now be
// properly reset so they can increase like the other ones -Bane
for(a=POI; a<= SPL;a++)
saves[a].gained = 0;
}
// Give out skills here
if(lGain->hasSkills()) {
print("Ok you should have some skills, seeing what they are.\n");
checkSkillsGain(lGain->getSkillBegin(), lGain->getSkillEnd());
} else {
print("No skills for you at this level.\n");
}
}
if(!negativeLevels) {
hp.restore();
mp.restore();
}
if(cClass == LICH && !relevel) {
if(level == 7)
print("Your body has deteriorated slightly.\n");
if(level == 13)
print("Your body has decayed immensely.\n");
if(level == 19)
print("What's left of your flesh hangs on your bones.\n");
if(level == 25)
print("You are now nothing but a dried up and brittle set of bones.\n");
}
if((cClass == MAGE || cClass2 == MAGE) && level == 7 && !relevel) {
print("You have learned the armor spell.\n");
learnSpell(S_ARMOR);
}
if( cClass == CLERIC &&
level >= 13 &&
deity == CERIS &&
!spellIsKnown(S_REJUVENATE)
) {
print("%s has granted you the rejuvinate spell.\n", gConfig->getDeity(deity)->getName().c_str());
learnSpell(S_REJUVENATE);
}
if( cClass == CLERIC &&
level >= 19 &&
deity == CERIS &&
!spellIsKnown(S_RESURRECT)
) {
print("%s has granted you the resurrect spell.\n", gConfig->getDeity(deity)->getName().c_str());
learnSpell(S_RESURRECT);
}
if( cClass == CLERIC &&
!cClass2 &&
level >= 22 &&
deity == ARAMON &&
!spellIsKnown(S_BLOODFUSION)
) {
print("%s has granted you the bloodfusion spell.\n", gConfig->getDeity(deity)->getName().c_str());
learnSpell(S_BLOODFUSION);
}
updateGuild(this, GUILD_LEVEL);
update();
// getting [custom] as a title lets you pick a new one,
// even if you have already picked one.
if(!relevel) {
}
/* if(cClass == BARD)
pick_song(player);*/
}
//*********************************************************************
// downLevel
//*********************************************************************
// This function is called when a player loses a level due to dying or
// for some other reason. The appropriate stats are downgraded.
void Player::downLevel() {
if(isStaff()) {
level--;
return;
}
PlayerClass *pClass = gConfig->classes[getClassString()];
LevelGain *lGain = 0;
// Check for level info
if(!pClass) {
print("Error: Can't find your class!\n");
if(!isStaff()) {
return;
}
return;
} else {
lGain = pClass->getLevelGain(level);
if(!lGain) {
return;
}
}
int saveLost = 0;
LevelInfo* levelInfo = statistics.getLevelInfo(level);
if(!levelInfo) {
saveLost = lGain->getSave();
} else {
saveLost = levelInfo->getSaveGain();
}
bstring toRemove = bstring("Level") + level;
hp.removeModifier(toRemove);
mp.removeModifier(toRemove);
if(strength.removeModifier(toRemove))
*this << "You have lost strength.\n";
if(dexterity.removeModifier(toRemove))
*this << "You have lost dexterity.\n";
if(constitution.removeModifier(toRemove))
*this << "You have lost constitution.\n";
if(intelligence.removeModifier(toRemove))
*this << "You have lost intelligence.\n";
if(piety.removeModifier(toRemove))
*this << "You have lost piety.\n";
level--;
hp.restore();
if(cClass != LICH)
mp.restore();
switch (saveLost) {
case POI:
saves[POI].chance -= 3;
if(!negativeLevels)
saves[POI].gained = 0;
print("You are now less resistant to poison.\n");
break;
case DEA:
saves[DEA].chance -= 3;
if(!negativeLevels)
saves[DEA].gained = 0;
print("You are now less able to resist traps and death.\n");
break;
case BRE:
saves[BRE].chance -= 3;
if(!negativeLevels)
saves[BRE].gained = 0;
print("You are now less resistant to breath weapons and explosions.\n");
break;
case MEN:
saves[MEN].chance -= 3;
if(!negativeLevels)
saves[MEN].gained = 0;
print("You are now less resistant to mind dominating attacks.\n");
break;
case SPL:
saves[SPL].chance -= 3;
if(!negativeLevels)
saves[SPL].gained = 0;
print("You are now less resistant to magical spells.\n");
break;
}
// }
updateGuild(this, GUILD_DIE);
setMonkDice();
}
//*********************************************************************
// cmdTrain
//*********************************************************************
// This function allows a player to train if they are in the correct
// training location and has enough gold and experience. If so, the
// character goes up a level.
int cmdTrain(Player* player, cmd* cmnd) {
unsigned long goldneeded=0, expneeded=0, bankneeded=0, maxgold=0;
if(player->getClass() == BUILDER) {
player->print("You don't need to do that!\n");
return(0);
}
if(!player->ableToDoCommand())
return(0);
if(player->isBlind()) {
player->printColor("^CYou can't do that! You're blind!\n");
return(0);
}
if(!player->flagIsSet(P_SECURITY_CHECK_OK)) {
player->print("You are not allowed to do that.\n");
return(0);
}
if(!player->flagIsSet(P_PASSWORD_CURRENT)) {
player->print("Your password is no longer current.\n");
player->print("In order to train, you must change it.\n");
player->print("Use the \"password\" command to do this.\n");
return(0);
}
if(!player->flagIsSet(P_CHOSEN_ALIGNMENT) && player->getLevel() == ALIGNMENT_LEVEL) {
player->print("You must choose your alignment before you can train to level %d.\n", ALIGNMENT_LEVEL+1);
player->print("Use the 'alignment' command to do so. HELP ALIGNMENT.\n");
return(0);
}
if(player->getNegativeLevels()) {
player->print("To train, all of your life essence must be present.\n");
return(0);
}
expneeded = gConfig->expNeeded(player->getLevel());
if(player->getLevel() < 19)
maxgold = 750000;
else if(player->getLevel() < 22)
maxgold = 2000000;
else
maxgold = ((player->getLevel()-22)*500000) + 3000000;
goldneeded = MIN(maxgold, expneeded / 2L);
if(player->getRace() == HUMAN)
goldneeded += goldneeded/3/10; // Humans have +10% training costs.
// Leveling cost temporarily suspended. -TC
if(player->getLevel() <= 3) // Free for levels 1-3 to train.
goldneeded = 0;
if(expneeded > player->getExperience()) {
player->print("You need %s more experience.\n", player->expToLevel(false).c_str());
return(0);
}
if((goldneeded > (player->coins[GOLD] + player->bank[GOLD])) && !player->flagIsSet(P_FREE_TRAIN)) {
player->print("You don't have enough gold.\n");
player->print("You need %ld gold to train.\n", goldneeded);
return(0);
}
if(player->getRoomParent()->whatTraining() != player->getClass()) {
player->print("This is not your training location.\n");
return(0);
}
if(player->getLevel() >= MAXALVL) {
player->print("You couldn't possibly train any more!\n");
return(0);
}
// Prevent power leveling!
if(player->flagIsSet(P_JUST_TRAINED)) {
player->print("You just trained! You must leave the room and re-enter.\n");
return(0);
}
if(!player->flagIsSet(P_FREE_TRAIN)) {
if(goldneeded > player->coins[GOLD]) {
bankneeded = goldneeded - player->coins[GOLD];
player->coins.set(0, GOLD);
player->bank.sub(bankneeded, GOLD);
player->print("You use %ld gold coins from your bank account.\n", bankneeded);
Bank::log(player->getCName(), "WITHDRAW (train) %ld [Balance: %ld]\n",
bankneeded, player->bank[GOLD]);
} else
player->coins.sub(goldneeded, GOLD);
}
gServer->logGold(GOLD_OUT, player, Money(goldneeded, GOLD), NULL, "Training");
doTrain(player);
if(!player->flagIsSet(P_CHOSEN_ALIGNMENT) && player->getLevel() == ALIGNMENT_LEVEL) {
player->print("You may now choose your alignment. You must do so before you can reach level %d.\n", ALIGNMENT_LEVEL+1);
player->print("Use the 'alignment' command to do so. HELP ALIGNMENT.\n");
}
return(0);
}
//*********************************************************************
// checkWeaponSkillGain
//*********************************************************************
void Player::checkWeaponSkillGain() {
int numWeapons = 0;
// Everyone gets a new weapon skill every title
if(((level+2)%3) == 0)
numWeapons ++;
switch(cClass) {
case FIGHTER:
if(!cClass2) {
if(level%4 == 0)
numWeapons++;
} else {
// Mutli fighters get weapon skills like other fighting classes
if(level%8 == 8)
numWeapons++;
}
break;
case BERSERKER:
if(level%6)
numWeapons++;
break;
case THIEF:
case RANGER:
case ROGUE:
case BARD:
case PALADIN:
case DEATHKNIGHT:
case ASSASSIN:
if(level/8)
numWeapons++;
break;
case CLERIC:
case DRUID:
case PUREBLOOD:
if(cClass2) {
// Cle/Ass
if(level/8)
numWeapons++;
} else {
if(level/12)
numWeapons++;
}
break;
case MAGE:
if(cClass2) {
if(level/12)
numWeapons++;
}
break;
default:
break;
}
if(numWeapons != 0) {
weaponTrains += numWeapons;
print("You can now learn %d more weapon skill(s)!\n", numWeapons);
}
}