/* * File: skills.cc * Summary: Skill exercising functions. * Written by: Linley Henzell */ #include "AppHdr.h" #include "skills.h" #include #include #include #include "externs.h" #include "itemprop.h" #include "notes.h" #include "output.h" #include "player.h" #include "random.h" #include "skills2.h" #include "spl-cast.h" #include "tutorial.h" // MAX_COST_LIMIT is the maximum XP amount it will cost to raise a skill // by 10 skill points (ie one standard practice). // // MAX_SPENDING_LIMIT is the maximum XP amount we allow the player to // spend on a skill in a single raise. // // Note that they don't have to be equal, but it is important to make // sure that they're set so that the spending limit will always allow // for 1 skill point to be earned. #define MAX_COST_LIMIT 250 #define MAX_SPENDING_LIMIT 250 static int _exercise2( int exsk ); // These values were calculated by running a simulation of gaining skills. // The goal is to try and match the old cost system which used the player's // experience level (which has a number of problems) so things shouldn't // seem too different to the player... but we still try to err on the // high side for the lower levels. -- bwr int skill_cost_needed( int level ) { // The average starting skill total is actually lower, but // some classes get about 2200, and they would probably be // start around skill cost level 3 if we used the average. -- bwr int ret = 2200; switch (level) { case 1: ret = 0; break; case 2: ret += 250; break; // 250 -- big because of initial 25 pool case 3: ret += 350; break; // 100 case 4: ret += 550; break; // 200 case 5: ret += 900; break; // 350 case 6: ret += 1300; break; // 400 case 7: ret += 1900; break; // 600 case 8: ret += 2800; break; // 900 case 9: ret += 4200; break; // 1400 case 10: ret += 5900; break; // 1700 case 11: ret += 9000; break; // 3100 default: ret += 9000 + (4000 * (level - 11)); break; } return (ret); } void calc_total_skill_points( void ) { int i; you.total_skill_points = 0; for (i = 0; i < NUM_SKILLS; i++) you.total_skill_points += you.skill_points[i]; for (i = 1; i <= 27; i++) if (you.total_skill_points < skill_cost_needed(i)) break; you.skill_cost_level = i - 1; #if DEBUG_DIAGNOSTICS you.redraw_experience = true; #endif } // skill_cost_level makes skills more expensive for more experienced characters // skill_level makes higher skills more expensive static int _calc_skill_cost( int skill_cost_level, int skill_level ) { int ret = 1 + skill_level; // does not yet allow for loss of skill levels. if (skill_level > 9) { ret *= (skill_level - 7); ret /= 3; } if (skill_cost_level > 4) ret += skill_cost_level - 4; if (skill_cost_level > 7) ret += skill_cost_level - 7; if (skill_cost_level > 10) ret += skill_cost_level - 10; if (skill_cost_level > 13) ret += skill_cost_level - 13; if (skill_cost_level > 16) ret += skill_cost_level - 16; if (skill_cost_level > 10) { ret *= (skill_cost_level - 5); ret /= 5; } if (skill_level > 7) ret += 1; if (skill_level > 9) ret += 2; if (skill_level > 11) ret += 3; if (skill_level > 13) ret += 4; if (skill_level > 15) ret += 5; if (ret > MAX_COST_LIMIT) ret = MAX_COST_LIMIT; return (ret); } // returns total number of skill points gained int exercise(int exsk, int deg) { int ret = 0; while (deg > 0) { if (you.exp_available <= 0 || you.skills[exsk] >= 27) break; if (you.practise_skill[exsk] || one_chance_in(4)) ret += _exercise2( exsk ); deg--; } if (ret) dprf("Exercised %s (deg: %d) by %d", skill_name(exsk), deg, ret); return (ret); } // end exercise() static int _exercise2(int exsk) { int deg = 10; int bonus = 0; char old_best_skill = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99); // Being good at some weapons makes others easier to learn. if (exsk < SK_ARMOUR) { // Short Blades and Long Blades. if ((exsk == SK_SHORT_BLADES || exsk == SK_LONG_BLADES) && (you.skills[SK_SHORT_BLADES] > you.skills[exsk] || you.skills[SK_LONG_BLADES] > you.skills[exsk])) { bonus += random2(30); } // Axes and Polearms. if ((exsk == SK_AXES || exsk == SK_POLEARMS) && (you.skills[SK_AXES] > you.skills[exsk] || you.skills[SK_POLEARMS] > you.skills[exsk])) { bonus += random2(30); } // Polearms and Staves. if ((exsk == SK_POLEARMS || exsk == SK_STAVES) && (you.skills[SK_POLEARMS] > you.skills[exsk] || you.skills[SK_STAVES] > you.skills[exsk])) { bonus += random2(30); } // Axes and Maces. if ((exsk == SK_AXES || exsk == SK_MACES_FLAILS) && (you.skills[SK_AXES] > you.skills[exsk] || you.skills[SK_MACES_FLAILS] > you.skills[exsk])) { bonus += random2(30); } // Maces and Staves. if ((exsk == SK_MACES_FLAILS || exsk == SK_STAVES) && (you.skills[SK_MACES_FLAILS] > you.skills[exsk] || you.skills[SK_STAVES] > you.skills[exsk])) { bonus += random2(30); } // Slings and Throwing. if ((exsk == SK_SLINGS || exsk == SK_THROWING) && (you.skills[SK_SLINGS] > you.skills[exsk] || you.skills[SK_THROWING] > you.skills[exsk])) { bonus += random2(30); } } // Quick fix for the fact that stealth can't be gained fast enough // to keep up with the monster levels. This should speed its // advancement. if (exsk == SK_STEALTH) bonus += random2(30); int skill_change = _calc_skill_cost(you.skill_cost_level, you.skills[exsk]); // Spellcasting is cheaper early on, and elementals hinder each // other. if (exsk >= SK_SPELLCASTING) { if (you.skill_cost_level < 5) skill_change /= 2; else if (you.skill_cost_level < 15) { skill_change *= (10 + (you.skill_cost_level - 5)); skill_change /= 20; } // Being good at elemental magic makes other elements harder to // learn. if (exsk >= SK_FIRE_MAGIC && exsk <= SK_EARTH_MAGIC && (you.skills[SK_FIRE_MAGIC] > you.skills[exsk] || you.skills[SK_ICE_MAGIC] > you.skills[exsk] || you.skills[SK_AIR_MAGIC] > you.skills[exsk] || you.skills[SK_EARTH_MAGIC] > you.skills[exsk])) { if (one_chance_in(3)) return (0); } // Some are direct opposites. if ((exsk == SK_FIRE_MAGIC || exsk == SK_ICE_MAGIC) && (you.skills[SK_FIRE_MAGIC] > you.skills[exsk] || you.skills[SK_ICE_MAGIC] > you.skills[exsk])) { // Of course, this is cumulative with the one above. if (!one_chance_in(3)) return (0); } if ((exsk == SK_AIR_MAGIC || exsk == SK_EARTH_MAGIC) && (you.skills[SK_AIR_MAGIC] > you.skills[exsk] || you.skills[SK_EARTH_MAGIC] > you.skills[exsk])) { if (!one_chance_in(3)) return (0); } // Experimental restriction (too many spell schools). -- bwr int skill_rank = 1; for (int i = SK_CONJURATIONS; i < SK_FIRE_MAGIC; ++i) { if (you.skills[exsk] < you.skills[i]) skill_rank++; } // Things get progressively harder, but not harder than // the Fire-Air or Ice-Earth level. if (skill_rank > 3 && one_chance_in(10 - skill_rank)) return (0); } int spending_limit = std::min(MAX_SPENDING_LIMIT, you.exp_available); // Handle fractional learning. if (skill_change > spending_limit) { // This system is a bit hard on missile weapons in the late // game, since they require expendable ammo in order to // practise. Increasing the "deg"ree of exercise would make // missile weapons too easy earlier on, so, instead, we're // giving them a special case here. if (exsk != SK_THROWING && exsk != SK_BOWS && exsk != SK_CROSSBOWS || skill_change > you.exp_available) { int fraction = (spending_limit * 10) / skill_change; deg = (deg * fraction) / 10; if (deg == 0) bonus = (bonus * fraction) / 10; } else deg = ((skill_change / 2) > MAX_SPENDING_LIMIT) ? 5 : 10; skill_change = spending_limit; } skill_change -= random2(5); if (skill_change <= 0) { // No free lunch. This is a problem now that we don't have // overspending. skill_change = (deg > 0 || bonus > 0) ? 1 : 0; } // We can safely return at any stage before this. int skill_inc = deg + bonus; // Starting to learn skills is easier if the appropriate stat is // high. if (you.skills[exsk] == 0) { if ((exsk >= SK_FIGHTING && exsk <= SK_STAVES) || exsk == SK_ARMOUR) { // These skills are easier for the strong. skill_inc *= std::max(5, you.strength); skill_inc /= 10; } else if (exsk >= SK_SLINGS && exsk <= SK_UNARMED_COMBAT) { // These skills are easier for the dexterous. // Note: Armour is handled above. skill_inc *= std::max(5, you.dex); skill_inc /= 10; } else if (exsk >= SK_SPELLCASTING && exsk <= SK_POISON_MAGIC) { // These skills are easier for the smart. skill_inc *= std::max(5, you.intel); skill_inc /= 10; } } if (skill_inc <= 0) return (0); you.skill_points[exsk] += skill_inc; you.exp_available -= skill_change; you.total_skill_points += skill_inc; if (you.skill_cost_level < 27 && you.total_skill_points >= skill_cost_needed(you.skill_cost_level + 1)) { you.skill_cost_level++; } you.exp_available = std::max(0, you.exp_available); you.redraw_experience = true; /* New (LH): debugging bit: when you exercise a skill, displays the skill exercised and how much you spent on it. Too irritating to be a regular WIZARD feature. #if DEBUG_DIAGNOSTICS mprf( MSGCH_DIAGNOSTICS, "Exercised %s * %d for %d xp.", skill_name(exsk), skill_inc, skill_change ); #endif */ if (you.skill_points[exsk] > (skill_exp_needed(you.skills[exsk] + 1) * species_skills(exsk, you.species) / 100)) { you.skills[exsk]++; take_note(Note(NOTE_GAIN_SKILL, exsk, you.skills[exsk])); if (you.skills[exsk] == 27) { mprf(MSGCH_INTRINSIC_GAIN, "You have mastered %s!", skill_name( exsk ) ); } else if (you.skills[exsk] == 1) { mprf(MSGCH_INTRINSIC_GAIN, "You have gained %s skill!", skill_name( exsk ) ); tut_gained_new_skill(exsk); } else { mprf(MSGCH_INTRINSIC_GAIN, "Your %s skill increases to level %d!", skill_name( exsk ), you.skills[exsk] ); } learned_something_new(TUT_SKILL_RAISE); // Recalculate this skill's order for tie breaking skills // at its new level. See skills2.cc::init_skill_order() // for more details. -- bwr you.skill_order[exsk] = 0; for (int i = SK_FIGHTING; i < NUM_SKILLS; ++i) { if (i == exsk) continue; if (you.skills[i] >= you.skills[exsk]) you.skill_order[exsk]++; } if (exsk == SK_FIGHTING) calc_hp(); if (exsk == SK_INVOCATIONS || exsk == SK_SPELLCASTING) calc_mp(); if (exsk == SK_DODGING || exsk == SK_ARMOUR) you.redraw_evasion = true; if (exsk == SK_ARMOUR || exsk == SK_SHIELDS || exsk == SK_ICE_MAGIC || exsk == SK_EARTH_MAGIC || you.duration[DUR_TRANSFORMATION] > 0) { you.redraw_armour_class = true; } const unsigned char best = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99); const unsigned char best_spell = best_skill(SK_SPELLCASTING, SK_POISON_MAGIC, 99); if (exsk == SK_SPELLCASTING && you.skills[exsk] == 1 && best_spell == SK_SPELLCASTING) { mpr("You're starting to get the hang of this magic thing."); } if (best != old_best_skill || old_best_skill == exsk) redraw_skill(you.your_name, player_title()); if (you.weapon() && item_is_staff(*you.weapon())) maybe_identify_staff(*you.weapon()); } return (skill_inc); }