/*
* skills.cpp
* Skill manipulation 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-2009 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 "skills.h"
#include "commands.h"
#include "clans.h"
#include <sstream>
#define NOT_A_SKILL -10
Skill::Skill() {
gainType = SKILL_NORMAL;
knownOnly = false;
}
int Skill::getGainType() const { return(gainType); }
int CrtSkill::getGainBonus() const { return(gainBonus); }
bool Skill::isKnownOnly() const { return(knownOnly); }
bool Config::isKnownOnly(const bstring& skillName) const {
std::map<bstring, Skill*>::const_iterator it = skills.find(skillName);
if(it != skills.end())
return(((*it).second)->isKnownOnly());
return(false);
}
//*****************************************************************
// CrtSkills - Keeps track of skill information for a Creature
//*****************************************************************
//--------------------------------------------------------------------
// Constructors
CrtSkill::CrtSkill() {
name = "";
gained = 0;
gainBonus = 0;
}
CrtSkill::CrtSkill(const bstring& pName, int pGained) {
name = pName;
gained = pGained;
gainBonus = 0;
}
// End constructors
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Get/Set Functions
bstring CrtSkill::getName() const { return(name); }
int CrtSkill::getGained() const { return(gained); }
int CrtSkill::getGainType() const {
Skill *s = gConfig->getSkill(name);
return(s ? s->getGainType() : NOT_A_SKILL);
}
bstring CrtSkill::getDisplayName() const { return(gConfig->getSkillDisplayName(name)); }
bstring CrtSkill::getGroup() const { return(gConfig->getSkillGroup(name)); }
void CrtSkill::setGained(int pGained) {
gained = pGained;
}
void CrtSkill::setName(bstring &pName) {
name = pName;
}
// End Get/Set Functions
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Misc Functions
void CrtSkill::upBonus(int amt) {
gainBonus += amt;
}
void CrtSkill::clearBonus() {
gainBonus = 0;
}
void CrtSkill::improve(int amt) {
gained += amt;
}
// End Misc Functions
//--------------------------------------------------------------------
//*****************************************************************
// Skill
//*****************************************************************
// Class to store base information about skills
//********************************************************************
// setGroup
//********************************************************************
// Sets group, aborts if not valid
bool Skill::setGroup(bstring &pGroup) {
ASSERTLOG(pGroup != "");
bstring groupName = gConfig->getSkillGroupDisplayName(pGroup);
if(groupName == "") {
return(false);
}
group = pGroup;
return true;
}
bstring Skill::getName() const { return(name); }
bstring Skill::getGroup() const { return(group); }
bstring Skill::getDisplayName() const { return(displayName); }
bstring Skill::getDescription() const { return(description); }
//********************************************************************
// checkImprove
//********************************************************************
// Parameters: skillName - What skill are we checking for improvement
// success - Was the skill sucessfull
// attribute - What attribute will be helpful in raising the skill? (default: INT)
// bns - Any bonus to the improve calculation (default: 0)
void Creature::checkImprove(const bstring& skillName, bool success, int attribute, int bns) {
if(isMonster())
return;
if(inJail())
return;
CrtSkill* crSkill = getSkill(skillName);
if(!crSkill)
return;
int gainType = crSkill->getGainType();
// not a skill!
if(gainType == NOT_A_SKILL) {
broadcast(::isDm, "^y*** Skill \"%s\" was requested by the mud, but was not\n found in the skill list. Check *skills to verify.", skillName.c_str());
return;
}
long j=0,t;
t = time(0);
j = LT(this, LT_SKILL_INCREASE);
//
if(t < j)
return;
int chance = 0;
int gained = crSkill->getGained();
chance = 100 - (gained/3);
// Chance is too high right now, reduce it by half
chance /= 2;
if(gained < 100) {
// You learn more from your failures at lower skill levels
if(!success)
chance *= 2;
} else {
// You learn less from failures at higher skill levels
if(!success)
chance /= 2;
}
if(gainType == SKILL_EASY) {
// make it harder
chance /= 2;
} else if(gainType != SKILL_NORMAL) {
chance += crSkill->getGainBonus();
}
// Unless max for level, 1% chance minimum
chance = MAX(chance, 1);
// 10 skill points per level possible, can't go over that
if(gained >= (level*10))
chance = 0;
int roll = mrand(1, 100);
if(roll <= chance) {
bool af = gConfig->isAprilFools();
if(success) {
printColor("Your practice %spays off, you have become %s at ^W%s^x!\n",
af ? "fails to " : "", af ? "worse" : "better",
gConfig->getSkillDisplayName(skillName).c_str());
} else {
printColor("You %slearn from your mistakes, you have become %s at ^W%s^x!\n",
af ? "fail to " : "", af ? "worse" : "better",
gConfig->getSkillDisplayName(skillName).c_str());
}
if(isPlayer()) {
lasttime[LT_SKILL_INCREASE].ltime = t;
// Length of wait is based on skill level, not player level
long wait = 10L * (gained/10);
wait = MIN(wait, 150L);
lasttime[LT_SKILL_INCREASE].interval = wait;
}
// TODO: Add a chance for a double improve for a hard skill
crSkill->clearBonus();
crSkill->improve();
} else {
// See if we have a hard skill on our hands, if so add a bonus to increase
if(gainType == SKILL_MEDIUM) {
crSkill->upBonus();
} else if(gainType == SKILL_HARD) {
crSkill->upBonus(2);
}
}
}
//********************************************************************
// knowsSkill
//********************************************************************
bool Creature::knowsSkill(const bstring& skillName) const {
if(isMonster()) return(true);
if(isCt()) return(true);
if(skillName == "") return(false);
std::map<bstring, CrtSkill*>::const_iterator csIt;
if( (csIt = skills.find(skillName)) == skills.end())
return(false);
else
return(true);
}
//********************************************************************
// getSkill
//********************************************************************
// Returns the requested skill if it can be found on the creature
CrtSkill* Creature::getSkill(const bstring& skillName) const {
if(skillName == "") return(false);
std::map<bstring, CrtSkill*>::const_iterator csIt;
if( (csIt = skills.find(skillName)) == skills.end())
return(NULL);
else
return((*csIt).second);
}
//********************************************************************
// addSkill
//********************************************************************
// Add a new skill of 'skillName' at 'gained' level
void Creature::addSkill(const bstring& skillName, int gained) {
if(skillName == "")
return;
CrtSkill* skill = new CrtSkill(skillName, gained);
skills[skillName] = skill;
}
//********************************************************************
// remSkill
//********************************************************************
void Creature::remSkill(const bstring& skillName) {
if(skillName == "")
return;
skills.erase(skillName);
}
#define SKILL_CHART_SIZE 21
char skillLevelStr[][SKILL_CHART_SIZE] =
{
"^rHorrible^x", // 0-24
"^rPoor^x", // 25-49
"^rFair^x", // 50-74
"^mMediocre^x", // 75-99
"^mDecent^x", // 100-124
"^mBelow Average^x", // 125-149
"^wCompetent^x", // 150-174
"^wAverage^x", // 175-199
"^wAbove Average^x", // 200-224
"^cProficient^x", // 225-249
"^cSkilled^x", // 250-274
"^cTalented^x", // 275-299
"^bVery Good^x", // 300-324
"^bAdept^x", // 325-349
"^bSuperb^x", // 350-374
"^gExceptional^x", // 375-399
"^gExpert^x", // 400-424
"^gMaster^x", // 425-449
"^ySuperior Master^x", // 450-474
"^yGrand Master^x", // 474-499
"^yGodlike^x" // 500
};
char craftSkillLevelStr[][25] =
{
"Novice",
"Apprentice",
"Journeyman",
"Expert",
"Artisian",
"Master",
"Grand Master"
};
//********************************************************************
// showSkills
//********************************************************************
// Show the skills and skill levels of 'player' to 'sock'
int showSkills(Player* toShow, Creature* player, bool magicOnly=false) {
std::map<bstring, bstring>::iterator sgIt;
std::map<bstring, CrtSkill*>::iterator sIt;
CrtSkill* crtSkill=0;
int known=0;
double skill=0;
const Clan *clan=0;
bool showProgress = player->flagIsSet(P_SHOW_SKILL_PROGRESS);
bool showDigits = !showProgress;
int barLength = 20;
if(player->getClan())
clan = gConfig->getClan(player->getClan());
// Tell the player what skills they are looking at
if(toShow->getPlayer() == player)
toShow->printColor("^YYour Skills:");
else
toShow->printColor("^Y%s's Skills:", player->name);
if(magicOnly)
toShow->printColor(" ^Ytype \"skills\" to show non-magical skills.");
else if(player->getClass() != BERSERKER)
toShow->printColor(" ^Ytype \"skills magic\" to show magical skills.");
toShow->print("\n");
for(sgIt = gConfig->skillGroups.begin() ; sgIt != gConfig->skillGroups.end() ; sgIt++) {
if( (*sgIt).first == "arcane" ||
(*sgIt).first == "divine" ||
(*sgIt).first == "magic"
) {
if(!magicOnly)
continue;
} else {
if(magicOnly)
continue;
}
std::ostringstream oStr;
known = 0;
oStr << "^W" << (*sgIt).second << "^x\n";
for(sIt = player->skills.begin() ; sIt != player->skills.end() ; sIt++) {
crtSkill = (*sIt).second;
if(crtSkill->getGroup() == (*sgIt).first) {
//1+player->saves[st].chance)/10
known++;
bool isCraft = crtSkill->getGroup() == "craft";
int curSkill = 0;
float maxSkill = 0;
if(isCraft) {
maxSkill = MIN(player->getLevel()*10, 100);
curSkill = MIN(crtSkill->getGained(), (int)maxSkill);
} else {
maxSkill = MIN(player->getLevel()*10.0, MAXALVL*10.0);
curSkill = MIN(crtSkill->getGained(), maxSkill);
}
skill = curSkill;
if(clan)
skill += clan->getSkillBonus(crtSkill->getName());
skill /= 25;
int displayNum = (int)skill;
displayNum = MAX(0, MIN(SKILL_CHART_SIZE-1, displayNum));
//bstring progressBar(int barLength, float percentFull, bstring text, char progressChar, bool enclosed)
oStr << " ";
if(showProgress) {
oStr << " ";
bstring progress = "" + bstring(curSkill) + "/" + bstring(maxSkill);
if(gConfig->isKnownOnly(crtSkill->getName()))
oStr << progressBar(barLength, 1);
else
oStr << progressBar(barLength, (1.0*curSkill)/maxSkill, progress.c_str());
}
oStr << " " << crtSkill->getDisplayName() << " - ";
if(gConfig->isKnownOnly(crtSkill->getName())) {
oStr << "^WKnown^x\n";
continue;
}
if(!isCraft) {
// Not a crafting skill
oStr << skillLevelStr[displayNum];
} else {
// Crafting Skill
int disp = (curSkill/maxSkill)*6;
oStr << craftSkillLevelStr[disp];
}
if(showDigits )
oStr << " (" << curSkill << ")";
skill = 0;
if(clan)
skill = clan->getSkillBonus(crtSkill->getName());
if((int)skill)
oStr << " (Clan: " << skill << ")";
if((*sIt).first == "defense" && player->isEffected("protection"))
oStr << " (Protection: 10)";
if(toShow->isCt() && !isCraft) {
// oStr << " (" << crtSkill->getGained() << ")";
oStr << " [" << player->getSkillLevel(crtSkill->getName()) << "]";
}
oStr << "\n";
}
}
if(known)
toShow->printColor("%s", oStr.str().c_str());
}
return(0);
}
//********************************************************************
// getSkillLevel
//********************************************************************
// Return the player level equilvalent of the given skill
double Creature::getSkillLevel(const bstring& skillName) const {
if(isMonster())
return(level);
CrtSkill* skill = getSkill(skillName);
if(skill==NULL) {
if(isCt())
return(MAXALVL);
else
return(0);
}
int gained = getSkillGained(skillName);
double level = 0.0;
if(gained <= 100) {
// If less than 100, adding 5 to help out lower levels
level = ((double)gained + 5.0) / 10.0;
} else {
// If over 100, divide by 9.5 to provide more returns for higher levels
level = (double)gained / 9.5;
}
if(clan) {
const Clan* c = gConfig->getClan(clan);
if(clan)
level += c->getSkillBonus(skillName);
}
return(level);
}
//********************************************************************
// getSkillGained
//********************************************************************
double Creature::getSkillGained(const bstring& skillName) const {
CrtSkill* skill = getSkill(skillName);
if(skill==NULL) {
if(isCt())
return(MAXALVL*10);
else
return(0);
}
double gained = (skill->getGained()*1.0);
// Prevents a level 30 from deleveling to 25 and still fighting like a level 30
if(gained/10 > level)
gained = level*10.0;
return(gained);
}
//********************************************************************
// dmSkills
//********************************************************************
// List the skill table, or optionally a player's known skills
int dmSkills(Player* player, cmd* cmnd) {
if(cmnd->num < 2) {
std::map<bstring, bstring>::iterator sgIt;
std::map<bstring, Skill*>::iterator sIt;
player->printColor("^xSkill Groups\n%-20s - %40s\n---------------------------------------------------------------\n", "Name", "DisplayName");
for(sgIt = gConfig->skillGroups.begin() ; sgIt != gConfig->skillGroups.end() ; sgIt++) {
player->print("%-20s - %40s\n", (*sgIt).first.c_str(), (*sgIt).second.c_str());
}
player->printColor("\n^xSkills\n%-20s - %20s - %-15s\n-------------------------------------------------------------\n", "Name", "DisplayName", "Group");
Skill* skill;
bstring curGroup;
for(sgIt = gConfig->skillGroups.begin() ; sgIt != gConfig->skillGroups.end() ; sgIt++) {
curGroup = (*sgIt).first;
for(sIt = gConfig->skills.begin() ; sIt != gConfig->skills.end() ; sIt++) {
skill = (*sIt).second;
if(skill->getGroup() == curGroup) {
char color[3] = "";
if(skill->getGainType() == SKILL_MEDIUM)
strcpy(color, "^y");
else if(skill->getGainType() == SKILL_HARD)
strcpy(color, "^r");
else if(skill->getGainType() == SKILL_EASY)
strcpy(color, "^g");
player->printColor("%s%-20s - %20s - %-15s\n",color,
skill->getName().c_str(), skill->getDisplayName().c_str(), skill->getGroup().c_str());
}
}
}
} else {
Creature* target=0;
cmnd->str[1][0] = up(cmnd->str[1][0]);
target = gServer->findPlayer(cmnd->str[1]);
cmnd->str[1][0] = low(cmnd->str[1][0]);
if(!target || (!player->isCt() && target->flagIsSet(P_DM_INVIS)))
target = player->getRoom()->findCreature(player, cmnd);
if(!target) {
player->print("Target not found.\n");
return(0);
}
player->print("Skills for: %s\n", target->name);
showSkills(player, target, true);
}
return(0);
}
//********************************************************************
// dmSetSkills
//********************************************************************
int dmSetSkills(Player *admin, cmd* cmnd) {
if(cmnd->num < 2) {
admin->print("Set skills for who?\n");
return(0);
}
Player* target=0;
cmnd->str[1][0] = up(cmnd->str[1][0]);
target = gServer->findPlayer(cmnd->str[1]);
if(!target) {
admin->print("No player logged on with that name.\n");
return(0);
}
PlayerClass *pClass = gConfig->classes[target->getClassString()];
if(pClass) {
target->checkSkillsGain(pClass->getSkillBegin(), pClass->getSkillEnd(), true);
LevelGain *lGain;
for(int level = 2 ; level <= target->getLevel() ; level++ ) {
lGain = pClass->getLevelGain(level);
if(!lGain) {
admin->print("Error: Can't find any information for level %d!\n", level);
} else {
if(lGain->hasSkills()) {
target->checkSkillsGain(lGain->getSkillBegin(), lGain->getSkillEnd(), true);
}
}
}
admin->print("%s's skills have been set for %s level.\n", target->name, target->hisHer());
} else {
admin->print("Unable to find class %s.\n", target->getClassString().c_str());
}
return(0);
}
//********************************************************************
// cmdSkills
//********************************************************************
// Display all skills a player knows and their level
int cmdSkills(Player* player, cmd* cmnd) {
Creature* target = player;
bool magicOnly=false;
int pos = 1;
magicOnly = cmnd->str[1][0] && !strncmp(cmnd->str[1], "magic", strlen(cmnd->str[1]));
if(player->isCt()) {
if(magicOnly)
pos = 2;
if(cmnd->str[pos][0]) {
cmnd->str[pos][0] = up(cmnd->str[pos][0]);
target = gServer->findPlayer(cmnd->str[pos]);
if(!target) {
player->print("No player logged on with that name.\n");
return(0);
}
}
}
showSkills(player, target, magicOnly);
return(0);
}
// End Skill functions related to Creatures
//--------------------------------------------------------------------
struct {
const char *songstr;
int songno;
int (*songfn)();
} songlist[] = {
{ "healing", SONG_HEAL, (int(*)())songHeal },
{ "magic", SONG_MP, (int(*)())songMPHeal },
{ "restoration", SONG_RESTORE, (int(*)())songRestore },
{ "destruction", SONG_DESTRUCTION, (int(*)())songOffensive },
{ "mass-destruction", SONG_MASS_DESTRUCTION, (int(*)())songMultiOffensive },
{ "holiness", SONG_BLESS, (int(*)())songBless },
{ "protection", SONG_PROTECTION, (int(*)())songProtection },
{ "flight", SONG_FLIGHT, (int(*)())songFlight },
{ "recall", SONG_RECALL, (int(*)())songRecall },
{ "safety", SONG_SAFETY, (int(*)())songSafety },
{ "@", -1, 0 }
};
int songlist_size = sizeof(songlist)/sizeof(*songlist);
//**********************************************************************
// get_song_name
//**********************************************************************
const char *get_song_name(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < songlist_size);
nIndex = MAX(0, MIN(nIndex, songlist_size));
return(songlist[nIndex].songstr);
}
//**********************************************************************
// get_song_num()
//**********************************************************************
int get_song_num(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < songlist_size);
nIndex = MAX(0, MIN(nIndex, songlist_size));
return(songlist[nIndex].songno);
}
//**********************************************************************
// get_song_function()
//**********************************************************************
SONGFN get_song_function(int nIndex) {
// do bounds checking
ASSERTLOG(nIndex >= 0);
ASSERTLOG(nIndex < songlist_size);
nIndex = MAX(0, MIN(nIndex, songlist_size));
return(songlist[nIndex].songfn);
}
//**********************************************************************
// getMaxSong
//**********************************************************************
int Config::getMaxSong() {
return(songlist_size-1);
}
// Clears skills
void Config::clearSkills() {
// Clear skill groups
skillGroups.clear();
// Clear & delete skills
std::map<bstring, Skill*>::iterator sIt;
Skill* skill;
for(sIt = skills.begin() ; sIt != skills.end() ; sIt++) {
skill = (*sIt).second;
delete skill;
//skills.erase(sIt);
}
skills.clear();
}
//********************************************************************
// skillExists
//********************************************************************
// True if the skill exists
bool Config::skillExists(const bstring& skillName) const {
std::map<bstring, Skill*>::const_iterator it = skills.find(skillName);
return(it != skills.end());
}
//********************************************************************
// getSkill
//********************************************************************
// Returns the given skill skill
Skill* Config::getSkill(const bstring& skillName) const {
std::map<bstring, Skill*>::const_iterator it = skills.find(skillName);
if(it != skills.end())
return((*it).second);
return(0);
}
//********************************************************************
// getSkillDisplayName
//********************************************************************
// Get the display name of the skill
bstring Config::getSkillDisplayName(const bstring& skillName) const {
std::map<bstring, Skill*>::const_iterator it = skills.find(skillName);
if(it != skills.end())
return(((*it).second)->getDisplayName());
return("");
}
//********************************************************************
// getSkillGroupDisplayName
//********************************************************************
// Get the group display name of the skill
bstring Config::getSkillGroupDisplayName(const bstring& groupName) const {
std::map<bstring, bstring>::const_iterator it = skillGroups.find(groupName);
if(it != skillGroups.end())
return((*it).second);
return("");
}
//********************************************************************
// getSkillGroup
//********************************************************************
// Get the skill group of the skill
bstring Config::getSkillGroup(const bstring& skillName) const {
std::map<bstring, Skill*>::const_iterator it = skills.find(skillName);
if(it != skills.end())
return(((*it).second)->getGroup());
return("");
}
// End Skill Related Functions
//--------------------------------------------------------------------