/* * 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-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 "skills.h" #include "commands.h" #include "clans.h" #include <sstream> #define NOT_A_SKILL -10 int SkillInfo::getGainType() const { return (gainType); } int Skill::getGainBonus() const { return (gainBonus); } bool SkillInfo::isKnownOnly() const { return (knownOnly); } bool SkillInfo::hasBaseSkill() const { return (!baseSkill.empty()); } bool Config::isKnownOnly(const bstring& skillName) const { std::map<bstring, SkillInfo*>::const_iterator it = skills.find(skillName); if (it != skills.end()) return (((*it).second)->isKnownOnly()); return (false); } //***************************************************************** // Skill - Keeps track of skill information for a Creature //***************************************************************** Skill::Skill() { reset(); } Skill::Skill(const bstring& pName, int pGained) { reset(); setName(pName); gained = pGained; } void Skill::reset() { name = ""; gained = 0; gainBonus = 0; skillInfo = 0; } // End constructors //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Get/Set Functions void SkillInfo::setName(bstring pName) { name = pName; } void SkillCommand::setName(bstring pName) { SkillInfo::name = pName; Command::name = pName; } bstring Skill::getName() const { return (name); } SkillInfo* Skill::getSkillInfo() { return (skillInfo); } int Skill::getGained() const { return (gained); } int Skill::getGainType() const { return (skillInfo ? skillInfo->getGainType() : NOT_A_SKILL); } bool Skill::hasBaseSkill() const { return (skillInfo && skillInfo->hasBaseSkill()); } bstring Skill::getBaseSkill() { return (skillInfo ? skillInfo->getBaseSkill() : ""); } bstring Skill::getDisplayName() const { return (skillInfo ? skillInfo->getDisplayName() : ""); } bstring Skill::getGroup() const { return (skillInfo ? skillInfo->getGroup() : ""); } void Skill::setGained(int pGained) { gained = pGained; } void Skill::setName(bstring pName) { name = pName; updateParent(); } void Skill::updateParent() { skillInfo = gConfig->getSkill(name); } // End Get/Set Functions //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Misc Functions void Skill::upBonus(int amt) { gainBonus += amt; } void Skill::clearBonus() { gainBonus = 0; } void Skill::improve(int amt) { gained += amt; } // End Misc Functions //-------------------------------------------------------------------- //***************************************************************** // Skill //***************************************************************** // Class to store base information about skills //******************************************************************** // setGroup //******************************************************************** // Sets group, aborts if not valid bool SkillInfo::setGroup(bstring &pGroup) { ASSERTLOG(pGroup != ""); bstring groupName = gConfig->getSkillGroupDisplayName(pGroup); if (groupName == "") { return (false); } group = pGroup; return true; } bool SkillInfo::setBase(bstring &pBase) { ASSERTLOG(pBase != ""); SkillInfo* skInfo = gConfig->getSkill(pBase); if (skInfo == NULL || skInfo == this || pBase == name) { return (false); } baseSkill = pBase; return true; } //bstring SkillInfo::getName() const { return(name); } bstring SkillInfo::getGroup() const { return (group); } bstring SkillInfo::getBaseSkill() const { return (baseSkill); } bstring SkillInfo::getDisplayName() const { return (displayName); } //bstring SkillInfo::getDescription() const { return(description); } //******************************************************************** // checkImprove //******************************************************************** // Check for an increase to the given skill; if there's a base skill, check // that instead // 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; Skill* 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, 3% chance minimum chance = MAX(chance, 3); // 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", crSkill->getDisplayName().c_str()); } else { printColor("You %slearn from your mistakes, you have become %s at ^W%s^x!\n", af ? "fail to " : "", af ? "worse" : "better", crSkill->getDisplayName().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; } // Chance for a double improve for a hard skill if (gainType == SKILL_HARD && mrand(1,100) < 33) crSkill->improve(2); else crSkill->improve(); crSkill->clearBonus(); } 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, Skill*>::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 Skill* Creature::getSkill(const bstring& skillName, bool useBase) const { if (skillName == "") return (null); SkillMap::const_iterator csIt = skills.find(skillName); if (csIt == skills.end()) return (NULL); else { Skill* toReturn = (*csIt).second; if (useBase) { SkillInfo* skillInfo = toReturn->getSkillInfo(); if (skillInfo && skillInfo->hasBaseSkill()) { Skill* baseSkill = getSkill(skillInfo->getBaseSkill()); if (baseSkill) return (baseSkill); } } return (toReturn); } } //********************************************************************* // setSkill //********************************************************************* bool Creature::setSkill(const bstring skillStr, int gained) { if(!gConfig->skillExists(skillStr)) return(false); // TODO: Handle adding of parent skill here Skill* skill = getSkill(skillStr, false); if(skill) { if(skill->hasBaseSkill()) { Skill* baseSkill = getSkill(skill->getBaseSkill(), false); if(!baseSkill) addSkill(skill->getBaseSkill(), gained); else setSkill(skill->getBaseSkill(), gained); } skill->setGained(gained); } else { addSkill(skillStr, gained); } return(true); } //******************************************************************** // addSkill //******************************************************************** // Add a new skill of 'skillName' at 'gained' level void Creature::addSkill(const bstring& skillName, int gained) { if (skillName == "" || getSkill(skillName, false) != NULL) return; Skill* skill = new Skill(skillName, gained); skills[skillName] = skill; // Add any base skill we need as well SkillInfo* skillInfo = skill->getSkillInfo(); if (skillInfo && skillInfo->hasBaseSkill()) { if (!knowsSkill(skillInfo->getBaseSkill())) { addSkill(skillInfo->getBaseSkill(), gained); } } } //******************************************************************** // remSkill //******************************************************************** void Creature::remSkill(const bstring& skillName) { if (skillName == "") return; SkillMap::iterator it = skills.find(skillName); if (it == skills.end()) return; Skill* skill = (*it).second; if (!skill) return; delete skill; 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 showMagic = false, bool showWeapons = false) { std::map<bstring, bstring>::iterator sgIt; std::map<bstring, Skill*>::iterator sIt; Skill* 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->getAsPlayer() == player) toShow->printColor("^YYour Skills:"); else toShow->printColor("^Y%s's Skills:", player->getCName()); if (showMagic) 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 (!showMagic) continue; } else { if (showMagic) 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, bool useBase) const { if (isMonster()) return (level); Skill* skill = getSkill(skillName, useBase); if (!skill) { if (isCt()) return (MAXALVL); else return (0); } int gained = getSkillGained(skillName); if (clan) { const Clan* c = gConfig->getClan(clan); if (clan) gained += c->getSkillBonus(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; } return (level); } //******************************************************************** // getSkillGained //******************************************************************** double Creature::getSkillGained(const bstring& skillName, bool useBase) const { Skill* skill = getSkill(skillName, useBase); 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); } double Creature::getTradeSkillGained(const bstring& skillName, bool useBase) const { Skill* skill = getSkill(skillName, useBase); if (skill == NULL) { if (isCt()) return (MAXALVL * 10); else return (0); } double gained = (skill->getGained() * 1.0); if (level < 7) { gained = MIN(gained, 75.0); } else if (level < 13) { gained = MIN(gained, 150.0); } else { gained = MIN(gained, 300.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, SkillInfo*>::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"); SkillInfo* 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->getParent()->findCreature(player, cmnd); if (!target) { player->print("Target not found.\n"); return (0); } player->print("Skills for: %s\n", target->getCName()); showSkills(player, target, true); } return (0); } //******************************************************************** // dmSetSkills //******************************************************************** int dmSetSkills(Player *admin, cmd* cmnd) { Player* target = 0; if (cmnd->num < 2) { admin->print("Set skills for who?\n"); return (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->getCName(), 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; bool online = true; 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* pTarget = 0; loadPlayer(cmnd->str[pos], &pTarget); target = pTarget; online = false; } if (!target) { player->print("No player logged on with that name.\n"); return (0); } if (target->isDm() && !player->isDm()) { player->print("You cannot view that player's skills.\n"); if (!online) free_crt(target); return (0); } } } showSkills(player, target, magicOnly); if (!online) free_crt(target); 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 SkillInfoMap::iterator sIt; SkillInfo* skill; for (sIt = skills.begin(); sIt != skills.end(); sIt++) { skill = (*sIt).second; delete skill; //skills.erase(sIt); } skills.clear(); // Every skill command is in the skills list, so they've already been erased, just clear them skillCommands.clear(); } // Updates skill pointers on players void Config::updateSkillPointers() { for (PlayerMap::value_type pp : gServer->players) { for (SkillMap::value_type sp : pp.second->skills) { sp.second->updateParent(); } } } //******************************************************************** // skillExists //******************************************************************** // True if the skill exists bool Config::skillExists(const bstring& skillName) const { std::map<bstring, SkillInfo*>::const_iterator it = skills.find(skillName); return (it != skills.end()); } //******************************************************************** // getSkill //******************************************************************** // Returns the given skill skill SkillInfo* Config::getSkill(const bstring& skillName) const { std::map<bstring, SkillInfo*>::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, SkillInfo*>::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, SkillInfo*>::const_iterator it = skills.find(skillName); if (it != skills.end()) return (((*it).second)->getGroup()); return (""); } // End Skill Related Functions //-------------------------------------------------------------------- SkillInfo::SkillInfo() { gainType = SKILL_NORMAL; knownOnly = false; }