1 /**
2  * @file
3  * @brief Skill exercising functions.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "skills.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstdlib>
13 #include <cstring>
14 #include <functional>
15 #include <sstream>
16 
17 #include "ability.h"
18 #include "clua.h"
19 #include "describe-god.h"
20 #include "evoke.h"
21 #include "files.h"
22 #include "god-abil.h"
23 #include "god-conduct.h"
24 #include "god-passive.h"
25 #include "hints.h"
26 #include "item-prop.h"
27 #include "libutil.h"
28 #include "message.h"
29 #include "notes.h"
30 #include "output.h"
31 #include "random.h"
32 #include "religion.h"
33 #include "skill-menu.h"
34 #include "sprint.h"
35 #include "state.h"
36 #include "stringutil.h"
37 #include "tag-version.h"
38 
39 // MAX_COST_LIMIT is the maximum XP amount it will cost to raise a skill
40 //                by 10 skill points (ie one standard practice).
41 //
42 // MAX_SPENDING_LIMIT is the maximum XP amount we allow the player to
43 //                    spend on a skill in a single raise.
44 //
45 // Note that they don't have to be equal, but it is important to make
46 // sure that they're set so that the spending limit will always allow
47 // for 1 skill point to be earned.
48 #define MAX_COST_LIMIT           265
49 #define MAX_SPENDING_LIMIT       265
50 
51 static int _train(skill_type exsk, int &max_exp, bool simu = false);
52 static void _train_skills(int exp, const int cost, const bool simu);
53 static int _training_target_skill_point_diff(skill_type exsk, int training_target);
54 
55 // Basic goals for titles:
56 // The higher titles must come last.
57 // Referring to the skill itself is fine ("Transmuter") but not impressive.
58 // No overlaps, high diversity.
59 
60 // See the map "replacements" below for what @Genus@, @Adj@, etc. do.
61 // NOTE: Even though @foo@ could be used with most of these, remember that
62 // the character's race will be listed on the next line. It's only really
63 // intended for cases where things might be really awkward without it. -- bwr
64 
65 // NOTE: If a skill name is changed, remember to also adapt the database entry.
66 static const char *skill_titles[NUM_SKILLS][7] =
67 {
68   //  Skill name        levels 1-7       levels 8-14        levels 15-20       levels 21-26      level 27       skill abbr
69     {"Fighting",       "Skirmisher",    "Fighter",         "Warrior",         "Slayer",         "Conqueror",    "Fgt"},
70     {"Short Blades",   "Cutter",        "Slicer",          "Swashbuckler",    "Cutthroat",      "Politician",   "SBl"},
71     {"Long Blades",    "Slasher",       "Carver",          "Fencer",          "@Adj@ Blade",    "Swordmaster",  "LBl"},
72     {"Axes",           "Chopper",       "Cleaver",         "Severer",         "Executioner",    "Axe Maniac",   "Axs"},
73     {"Maces & Flails", "Cudgeller",     "Basher",          "Bludgeoner",      "Shatterer",      "Skullcrusher", "M&F"},
74     {"Polearms",       "Poker",         "Spear-Bearer",    "Impaler",         "Phalangite",     "@Adj@ Porcupine", "Pla"},
75     {"Staves",         "Twirler",       "Cruncher",        "Stickfighter",    "Pulveriser",     "Chief of Staff", "Stv"},
76     {"Slings",         "Vandal",        "Slinger",         "Whirler",         "Slingshot",      "@Adj@ Catapult", "Slg"},
77     {"Bows",           "Shooter",       "Archer",          "Marks@genus@",    "Crack Shot",     "Merry @Genus@",  "Bws"},
78     {"Crossbows",      "Bolt Thrower",  "Quickloader",     "Sharpshooter",    "Sniper",         "@Adj@ Arbalest", "Crb"},
79     {"Throwing",       "Chucker",       "Thrower",         "Deadly Accurate", "Hawkeye",        "@Adj@ Ballista", "Thr"},
80     {"Armour",         "Covered",       "Protected",       "Tortoise",        "Impregnable",    "Invulnerable", "Arm"},
81     {"Dodging",        "Ducker",        "Nimble",          "Spry",            "Acrobat",        "Intangible",   "Ddg"},
82     {"Stealth",        "Sneak",         "Covert",          "Unseen",          "Imperceptible",  "Ninja",        "Sth"},
83 #if TAG_MAJOR_VERSION == 34
84     {"Stabbing",       "Miscreant",     "Blackguard",      "Backstabber",     "Cutthroat",      "Politician",   "Stb"},
85 #endif
86     {"Shields",        "Shield-Bearer", "Blocker",         "Peltast",         "Hoplite",        "@Adj@ Barricade", "Shd"},
87 #if TAG_MAJOR_VERSION == 34
88     {"Traps",          "Scout",         "Disarmer",        "Vigilant",        "Perceptive",     "Dungeon Master", "Trp"},
89 #endif
90     // STR based fighters, for DEX/martial arts titles see below. Felids get their own category, too.
91     {"Unarmed Combat", "Ruffian",       "Grappler",        "Brawler",         "Wrestler",       "@Weight@weight Champion", "UC"},
92 
93     {"Spellcasting",   "Magician",      "Thaumaturge",     "Eclecticist",     "Sorcerer",       "Archmage",     "Spc"},
94     {"Conjurations",   "Conjurer",      "Destroyer",       "Devastator",      "Ruinous",        "Annihilator",  "Conj"},
95     {"Hexes",          "Vexing",        "Jinx",            "Bewitcher",       "Maledictor",     "Spellbinder",  "Hex"},
96 #if TAG_MAJOR_VERSION == 34
97     {"Charms",         "Charmwright",   "Infuser",         "Anointer",        "Gracecrafter",   "Miracle Worker", "Chrm"},
98 #endif
99     {"Summonings",     "Caller",        "Summoner",        "Convoker",        "Worldbinder",    "Planerender",  "Summ"},
100     {"Necromancy",     "Grave Robber",  "Reanimator",      "Necromancer",     "Thanatomancer",  "@Genus_Short@ of Death", "Necr"},
101     {"Translocations", "Grasshopper",   "Placeless @Genus@", "Blinker",       "Portalist",      "Plane @Walker@", "Tloc"},
102     {"Transmutations", "Changer",       "Transmogrifier",  "Alchemist",       "Malleable",      "Shapeless @Genus@", "Tmut"},
103 
104     {"Fire Magic",     "Firebug",       "Arsonist",        "Scorcher",        "Pyromancer",     "Infernalist",  "Fire"},
105     {"Ice Magic",      "Chiller",       "Frost Mage",      "Gelid",           "Cryomancer",     "Englaciator",  "Ice"},
106     {"Air Magic",      "Gusty",         "Zephyrmancer",    "Stormcaller",     "Cloud Mage",     "Meteorologist", "Air"},
107     {"Earth Magic",    "Digger",        "Geomancer",       "Earth Mage",      "Metallomancer",  "Petrodigitator", "Erth"},
108     {"Poison Magic",   "Stinger",       "Tainter",         "Polluter",        "Contaminator",   "Envenomancer", "Pois"},
109 
110     // These titles apply to atheists only, worshippers of the various gods
111     // use the god titles instead, depending on piety or, in Gozag's case, gold.
112     // or, in U's case, invocations skill.
113     {"Invocations",    "Unbeliever",    "Agnostic",        "Dissident",       "Heretic",        "Apostate",     "Invo"},
114     {"Evocations",     "Charlatan",     "Prestidigitator", "Fetichist",       "Evocator",       "Talismancer",  "Evo"},
115 };
116 
117 static const char *martial_arts_titles[6] =
118     {"Unarmed Combat", "Insei", "Martial Artist", "Black Belt", "Sensei", "Grand Master"};
119 static const char *claw_and_tooth_titles[6] =
120     {"Unarmed Combat", "Scratcher", "Gouger", "Ripper", "Eviscerator", "Sabretooth"};
121 
122 struct species_skill_aptitude
123 {
124     species_type species;
125     skill_type   skill;
126     int aptitude;          // -50..50, with 0 for humans
127 
species_skill_aptitudespecies_skill_aptitude128     species_skill_aptitude(species_type _species,
129                            skill_type _skill,
130                            int _aptitude)
131         : species(_species), skill(_skill), aptitude(_aptitude)
132     {
133     }
134 };
135 
136 #include "aptitudes.h"
137 
138 // Traditionally, Spellcasting and In/Evocations formed the exceptions here:
139 // Spellcasting skill was more expensive with about 130%, the other two got
140 // a discount with about 75%.
141 static int _spec_skills[NUM_SPECIES][NUM_SKILLS];
142 
143 // The progress of skill_cost_level depends only on total experience points,
144 // it's independent of species. We try to keep close to the old system
145 // and use an experience aptitude of 130 as a reference (Tengu).
146 // This means that for a species with 130 exp apt, skill_cost_level should be
147 // the same as XL (unless the player has been drained).
148 
149 // 130 exp apt is midway between +0 and -1 now. -- elliptic
skill_cost_needed(int level)150 unsigned int skill_cost_needed(int level)
151 {
152     return exp_needed(level, 1) * 13;
153 }
154 
155 static const int MAX_SKILL_COST_LEVEL = 27;
156 
157 // skill_cost_level makes skills more expensive for more experienced characters
calc_skill_cost(int skill_cost_level)158 int calc_skill_cost(int skill_cost_level)
159 {
160     const int cost[] = { 1, 2, 3, 4, 5,            // 1-5
161                          7, 8, 9, 13, 22,         // 6-10
162                          37, 48, 73, 98, 125,      // 11-15
163                          145, 170, 190, 212, 225,  // 16-20
164                          240, 255, 260, 265, 265,  // 21-25
165                          265, 265 };
166     COMPILE_CHECK(ARRAYSZ(cost) == MAX_SKILL_COST_LEVEL);
167 
168     ASSERT_RANGE(skill_cost_level, 1, MAX_SKILL_COST_LEVEL + 1);
169     return cost[skill_cost_level - 1];
170 }
171 
172 /**
173  * The baseline skill cost for the 'cost' interface on the m screen.
174  *
175  * @returns the XP needed to go from level 0 to level 1 with +0 apt.
176  */
skill_cost_baseline()177 int skill_cost_baseline()
178 {
179     return skill_exp_needed(1, SK_FIGHTING, SP_HUMAN)
180            - skill_exp_needed(0, SK_FIGHTING, SP_HUMAN);
181 }
182 
183 /**
184  * The skill cost to increase the given skill from its current level by one.
185  *
186  * @param sk the skill to check the player's level of
187  * @returns the XP needed to increase from floor(level) to ceiling(level)
188  */
one_level_cost(skill_type sk)189 int one_level_cost(skill_type sk)
190 {
191     if (you.skills[sk] >= MAX_SKILL_LEVEL)
192         return 0;
193     return skill_exp_needed(you.skills[sk] + 1, sk)
194            - skill_exp_needed(you.skills[sk], sk);
195 }
196 
197 /**
198  * The number displayed in the 'cost' interface on the m screen.
199  *
200  * @param sk the skill to compute the cost of
201  * @returns the cost of raising sk from floor(level) to ceiling(level),
202  *          as a multiple of skill_cost_baseline()
203  */
scaled_skill_cost(skill_type sk)204 float scaled_skill_cost(skill_type sk)
205 {
206     if (you.skills[sk] == MAX_SKILL_LEVEL || is_useless_skill(sk))
207         return 0;
208     int baseline = skill_cost_baseline();
209     int next_level = one_level_cost(sk);
210     if (you.skill_manual_points[sk])
211         baseline *= 2;
212 
213     return (float)next_level / baseline;
214 }
215 
216 // Characters are actually granted skill points, not skill levels.
217 // Here we take racial aptitudes into account in determining final
218 // skill levels.
reassess_starting_skills()219 void reassess_starting_skills()
220 {
221     // go backwards, need to do Dodging before Armour
222     // "sk >= SK_FIRST_SKILL" might be optimised away, so do this differently.
223     for (skill_type next = NUM_SKILLS; next > SK_FIRST_SKILL; )
224     {
225         skill_type sk = --next;
226         ASSERT(you.skills[sk] == 0 || !is_useless_skill(sk));
227 
228         // Grant the amount of skill points required for a human.
229         you.skill_points[sk] = you.skills[sk] ?
230             skill_exp_needed(you.skills[sk], sk, SP_HUMAN) + 1 : 0;
231 
232         if (sk == SK_DODGING && you.skills[SK_ARMOUR]
233             && (is_useless_skill(SK_ARMOUR)
234                 || you_can_wear(EQ_BODY_ARMOUR) != MB_TRUE))
235         {
236             // No one who can't wear mundane heavy armour should start with
237             // the Armour skill -- D:1 dragon armour is too unlikely.
238             you.skill_points[sk] += skill_exp_needed(you.skills[SK_ARMOUR],
239                 SK_ARMOUR, SP_HUMAN) + 1;
240             you.skills[SK_ARMOUR] = 0;
241         }
242 
243         if (!you.skill_points[sk])
244             continue;
245 
246         // Find out what level that earns this character.
247         you.skills[sk] = 0;
248 
249         for (int lvl = 1; lvl <= 8; ++lvl)
250         {
251             if (you.skill_points[sk] > skill_exp_needed(lvl, sk))
252                 you.skills[sk] = lvl;
253             else
254                 break;
255         }
256 
257         // Wanderers get at least 1 level in their skills.
258         if (you.char_class == JOB_WANDERER && you.skills[sk] < 1)
259         {
260             you.skill_points[sk] = skill_exp_needed(1, sk);
261             you.skills[sk] = 1;
262         }
263 
264         // Spellcasters should always have Spellcasting skill.
265         if (sk == SK_SPELLCASTING && you.skills[sk] < 1)
266         {
267             you.skill_points[sk] = skill_exp_needed(1, sk);
268             you.skills[sk] = 1;
269         }
270     }
271 }
272 
_change_skill_level(skill_type exsk,int n)273 static void _change_skill_level(skill_type exsk, int n)
274 {
275     ASSERT(n != 0);
276     bool need_reset = false;
277 
278     you.skills[exsk] = max(0, you.skills[exsk] + n);
279 
280     take_note(Note(n > 0 ? NOTE_GAIN_SKILL : NOTE_LOSE_SKILL,
281                    exsk, you.skills[exsk]));
282 
283     // are you drained/crosstrained/ash'd in the relevant skill?
284     const bool specify_base = you.skill(exsk, 1) != you.skill(exsk, 1, true);
285     if (you.skills[exsk] == MAX_SKILL_LEVEL)
286         mprf(MSGCH_INTRINSIC_GAIN, "You have mastered %s!", skill_name(exsk));
287     else if (abs(n) == 1 && you.num_turns)
288     {
289         mprf(MSGCH_INTRINSIC_GAIN, "Your %s%s skill %s to level %d!",
290              specify_base ? "base " : "",
291              skill_name(exsk), (n > 0) ? "increases" : "decreases",
292              you.skills[exsk]);
293     }
294     else if (you.num_turns)
295     {
296         mprf(MSGCH_INTRINSIC_GAIN, "Your %s%s skill %s %d levels and is now "
297              "at level %d!",
298              specify_base ? "base " : "",
299              skill_name(exsk),
300              (n > 0) ? "gained" : "lost",
301              abs(n), you.skills[exsk]);
302     }
303 
304     if (you.skills[exsk] == n && n > 0)
305         hints_gained_new_skill(exsk);
306 
307     if (n > 0 && you.num_turns)
308         learned_something_new(HINT_SKILL_RAISE);
309 
310     if (you.skills[exsk] - n == MAX_SKILL_LEVEL)
311     {
312         you.train[exsk] = TRAINING_ENABLED;
313         need_reset = true;
314     }
315 
316     if (exsk == SK_SPELLCASTING && you.skills[exsk] == n && n > 0)
317         learned_something_new(HINT_GAINED_SPELLCASTING);
318 
319     if (need_reset)
320         reset_training();
321 
322     // calc_hp() has to be called here because it currently doesn't work
323     // right if you.skills[] hasn't been updated yet.
324     if (exsk == SK_FIGHTING)
325         calc_hp(true, false);
326 }
327 
328 // Called whenever a skill is trained.
redraw_skill(skill_type exsk,skill_type old_best_skill,bool recalculate_order)329 void redraw_skill(skill_type exsk, skill_type old_best_skill, bool recalculate_order)
330 {
331     if (exsk == SK_FIGHTING)
332         calc_hp(true, false);
333 
334     if (exsk == SK_INVOCATIONS || exsk == SK_SPELLCASTING)
335         calc_mp();
336 
337     if (exsk == SK_DODGING || exsk == SK_ARMOUR)
338         you.redraw_evasion = true;
339 
340     if (exsk == SK_ARMOUR || exsk == SK_SHIELDS || exsk == SK_ICE_MAGIC
341         || exsk == SK_EARTH_MAGIC || you.duration[DUR_TRANSFORMATION] > 0)
342     {
343         you.redraw_armour_class = true;
344     }
345 
346     if (recalculate_order)
347     {
348         // Recalculate this skill's order for tie breaking skills
349         // at its new level.   See skills.cc::init_skill_order()
350         // for more details.  -- bwr
351         you.skill_order[exsk] = 0;
352         for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
353         {
354             if (sk != exsk && you.skill(sk, 10, true) >= you.skill(exsk, 10, true))
355                 you.skill_order[exsk]++;
356         }
357 
358         const skill_type best = best_skill(SK_FIRST_SKILL, SK_LAST_SKILL);
359         if (best != old_best_skill || old_best_skill == exsk)
360         {
361             you.redraw_title = true;
362             // The player symbol depends on best skill title.
363             update_player_symbol();
364         }
365     }
366 }
367 
calc_skill_level_change(skill_type sk,int starting_level,int sk_points)368 int calc_skill_level_change(skill_type sk, int starting_level, int sk_points)
369 {
370     int new_level = starting_level;
371     while (1)
372     {
373         if (new_level < MAX_SKILL_LEVEL
374             && sk_points >= (int) skill_exp_needed(new_level + 1, sk))
375         {
376             ++new_level;
377         }
378         else if (sk_points < (int) skill_exp_needed(new_level, sk))
379         {
380             new_level--;
381             ASSERT(new_level >= 0);
382         }
383         else
384             break;
385     }
386     return new_level;
387 }
388 
check_skill_level_change(skill_type sk,bool do_level_up)389 void check_skill_level_change(skill_type sk, bool do_level_up)
390 {
391     const int new_level = calc_skill_level_change(sk, you.skills[sk], you.skill_points[sk]);
392 
393     if (new_level != you.skills[sk])
394     {
395         if (do_level_up)
396             _change_skill_level(sk, new_level - you.skills[sk]);
397         else
398             you.skills[sk] = new_level;
399     }
400 }
401 
402 // Fill a queue in random order with the values of the array.
403 template <typename T, int SIZE>
_init_queue(list<skill_type> & queue,FixedVector<T,SIZE> & array)404 static void _init_queue(list<skill_type> &queue, FixedVector<T, SIZE> &array)
405 {
406     ASSERT(queue.empty());
407 
408     while (1)
409     {
410         skill_type sk = (skill_type)random_choose_weighted(array);
411         if (is_invalid_skill(sk))
412             break;
413         queue.push_back(sk);
414         --array[sk];
415     }
416 
417     ASSERT(queue.size() == (unsigned)EXERCISE_QUEUE_SIZE);
418 }
419 
_erase_from_skills_to_hide(const skill_set & can_train)420 static void _erase_from_skills_to_hide(const skill_set &can_train)
421 {
422     for (skill_type sk : can_train)
423         you.skills_to_hide.erase(sk);
424 }
425 
426 /*
427  * Check the inventory to see what skills are likely to be useful
428  * among the ones in you.skills_to_hide.
429  * Useful skills are removed from the set.
430  */
_check_inventory_skills()431 static void _check_inventory_skills()
432 {
433     for (const auto &item : you.inv)
434     {
435         // Exit early if there's no more skill to check.
436         if (you.skills_to_hide.empty())
437             return;
438 
439         skill_set skills;
440         if (!item.defined() || !item_skills(item, skills))
441             continue;
442 
443         _erase_from_skills_to_hide(skills);
444     }
445 }
446 
_check_spell_skills()447 static void _check_spell_skills()
448 {
449     for (spell_type spell : you.spells)
450     {
451         // Exit early if there's no more skill to check.
452         if (you.skills_to_hide.empty())
453             return;
454 
455         if (spell == SPELL_NO_SPELL)
456             continue;
457 
458         skill_set skills;
459         spell_skills(spell, skills);
460         _erase_from_skills_to_hide(skills);
461     }
462 }
463 
_check_abil_skills()464 static void _check_abil_skills()
465 {
466     for (ability_type abil : get_god_abilities())
467     {
468         // Exit early if there's no more skill to check.
469         if (you.skills_to_hide.empty())
470             return;
471 
472         you.skills_to_hide.erase(abil_skill(abil));
473     }
474 }
475 
skill_names(const skill_set & skills)476 string skill_names(const skill_set &skills)
477 {
478     return comma_separated_fn(begin(skills), end(skills), skill_name);
479 }
480 
_check_skills_to_show()481 static void _check_skills_to_show()
482 {
483     for (skill_type sk : you.skills_to_show)
484     {
485         if (is_invalid_skill(sk) || is_useless_skill(sk))
486             continue;
487 
488         you.should_show_skill.set(sk);
489     }
490 
491     reset_training();
492     you.skills_to_show.clear();
493 }
494 
_check_skills_to_hide()495 static void _check_skills_to_hide()
496 {
497     // Gnolls can't stop training skills.
498     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
499         return;
500 
501     _check_inventory_skills();
502     _check_spell_skills();
503     _check_abil_skills();
504 
505     if (you.skills_to_hide.empty())
506         return;
507 
508     skill_set skills;
509     for (skill_type sk : you.skills_to_hide)
510     {
511         if (is_invalid_skill(sk))
512             continue;
513         if (you.skill_manual_points[sk])
514             continue;
515 
516         if (skill_trained(sk) && you.training[sk])
517             skills.insert(sk);
518         you.should_show_skill.set(sk, false);
519     }
520 
521     reset_training();
522     you.skills_to_hide.clear();
523 }
524 
update_can_currently_train()525 void update_can_currently_train()
526 {
527     if (!you.skills_to_show.empty())
528         _check_skills_to_show();
529 
530     if (!you.skills_to_hide.empty())
531         _check_skills_to_hide();
532 }
533 
skill_default_shown(skill_type sk)534 bool skill_default_shown(skill_type sk)
535 {
536     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
537         return true;
538 
539     switch (sk)
540     {
541     case SK_FIGHTING:
542     case SK_ARMOUR:
543     case SK_DODGING:
544     case SK_STEALTH:
545     case SK_UNARMED_COMBAT:
546     case SK_SPELLCASTING:
547         return true;
548     default:
549         return false;
550     }
551 }
552 
553 /*
554  * Init the can_currently_train array by examining inventory and spell list to
555  * see which skills can be trained.
556  */
init_can_currently_train()557 void init_can_currently_train()
558 {
559     // Clear everything out, in case this isn't the first game.
560     you.skills_to_show.clear();
561     you.skills_to_hide.clear();
562     you.can_currently_train.reset();
563 
564     for (int i = 0; i < NUM_SKILLS; ++i)
565     {
566         const skill_type sk = skill_type(i);
567 
568         if (is_useless_skill(sk))
569             continue;
570 
571         you.can_currently_train.set(sk);
572         you.should_show_skill.set(sk);
573         if (!skill_default_shown(sk))
574             you.skills_to_hide.insert(sk);
575     }
576 
577     _check_skills_to_hide();
578 }
579 
init_train()580 void init_train()
581 {
582     for (int i = 0; i < NUM_SKILLS; ++i)
583     {
584         if (you.can_currently_train[i] && you.skill_points[i])
585             you.train[i] = you.train_alt[i] = TRAINING_ENABLED;
586         else
587         {
588             const bool gnoll_enable = you.has_mutation(MUT_DISTRIBUTED_TRAINING)
589                                         && !is_removed_skill((skill_type) i);
590             // Skills are on by default in auto mode and off in manual.
591             you.train[i] = (training_status) (gnoll_enable
592                                                 || you.auto_training);
593             you.train_alt[i] =
594                 (training_status) (gnoll_enable || !you.auto_training);
595         }
596     }
597 }
598 
_cmp_rest(const pair<skill_type,int64_t> & a,const pair<skill_type,int64_t> & b)599 static bool _cmp_rest(const pair<skill_type, int64_t>& a,
600                       const pair<skill_type, int64_t>& b)
601 {
602     return a.second < b.second;
603 }
604 
605 /**
606  * Scale an array.
607  *
608  * @param array The array to be scaled.
609  * @param scale The new scale of the array.
610  * @param exact When true, make sure that the sum of the array elements
611  *              is equal to the scale.
612  */
613 template <typename T, int SIZE>
_scale_array(FixedVector<T,SIZE> & array,int scale,bool exact)614 static void _scale_array(FixedVector<T, SIZE> &array, int scale, bool exact)
615 {
616     int64_t total = 0;
617     // First, we calculate the sum of the values to be scaled.
618     for (int i = 0; i < NUM_SKILLS; ++i)
619         total += array[i];
620 
621     vector<pair<skill_type, int64_t> > rests;
622     int scaled_total = 0;
623 
624     // All skills disabled, nothing to do.
625     if (!total)
626         return;
627 
628     // Now we scale the values.
629     for (int i = 0; i < NUM_SKILLS; ++i)
630         if (array[i] > 0)
631         {
632             int64_t result = (int64_t)array[i] * (int64_t)scale;
633             const int64_t rest = result % total;
634             if (rest)
635                 rests.emplace_back(skill_type(i), rest);
636             array[i] = (int)(result / total);
637             scaled_total += array[i];
638         }
639 
640     ASSERT(scaled_total <= scale);
641 
642     if (!exact || scaled_total == scale)
643         return;
644 
645     // We ensure that the percentage always add up to 100 by increasing the
646     // training for skills which had the higher rest from the above scaling.
647     sort(rests.begin(), rests.end(), _cmp_rest);
648     for (auto &rest : rests)
649     {
650         if (scaled_total >= scale)
651             break;
652 
653         ++array[rest.first];
654         ++scaled_total;
655     }
656 
657     ASSERT(scaled_total == scale);
658 }
659 
660 /*
661  * Init the training array by scaling down the skill_points array to 100.
662  * Used at game setup, when upgrading saves and when loading dump files.
663  */
init_training()664 void init_training()
665 {
666     FixedVector<unsigned int, NUM_SKILLS> skills;
667     skills.init(0);
668     for (int i = 0; i < NUM_SKILLS; ++i)
669         if (skill_trained(i))
670             skills[i] = sqr(you.skill_points[i]);
671 
672     _scale_array(skills, EXERCISE_QUEUE_SIZE, true);
673     _init_queue(you.exercises, skills);
674 
675     for (int i = 0; i < NUM_SKILLS; ++i)
676         skills[i] = sqr(you.skill_points[i]);
677 
678     _scale_array(skills, EXERCISE_QUEUE_SIZE, true);
679     _init_queue(you.exercises_all, skills);
680 
681     reset_training();
682 }
683 
skills_being_trained()684 bool skills_being_trained()
685 {
686     for (int i = 0; i < NUM_SKILLS; ++i)
687     {
688         skill_type sk = static_cast<skill_type>(i);
689         if (skill_trained(sk))
690             return true;
691     }
692     return false;
693 }
694 
695 // Make sure at least one skill is selected.
696 // If not, go to the skill menu and return true.
check_selected_skills()697 bool check_selected_skills()
698 {
699     if (skills_being_trained())
700         return false;
701     if (!trainable_skills())
702     {
703         if (!you.received_noskill_warning)
704         {
705             you.received_noskill_warning = true;
706             mpr("You cannot train any new skills!");
707         }
708         // It's possible to have no selectable skills, if they are all
709         // untrainable or level 27, so we don't assert.
710         return false;
711     }
712 
713     // Calling a user lua function here to allow enabling skills without user
714     // prompt (much like the callback auto_experience for the case of potion of
715     // experience).
716     if (clua.callbooleanfn(false, "skill_training_needed", nullptr))
717     {
718         // did the callback do anything?
719         if (skills_being_trained())
720             return true;
721     }
722 
723     if (crawl_state.seen_hups)
724     {
725         save_game(true, "Game saved, see you later!");
726         return false;
727     }
728 
729     mpr("You need to enable at least one skill for training.");
730     // Training will be fixed up on load if this ASSERT triggers.
731     ASSERT(!you.has_mutation(MUT_DISTRIBUTED_TRAINING));
732     more();
733     reset_training();
734     skill_menu();
735     redraw_screen();
736     update_screen();
737     return true;
738 }
739 
740 /**
741  * Reset the training array. Disabled skills are skipped.
742  * In automatic mode, we use values from the exercise queue.
743  * In manual mode, all enabled skills are set to the same value.
744  * Result is scaled back to 100.
745  */
reset_training()746 void reset_training()
747 {
748     // Disable this here since we don't want any autotraining related skilling
749     // changes for Gnolls.
750     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
751         you.auto_training = false;
752 
753     // We clear the values in the training array. In auto mode they are set
754     // to 0 (and filled later with the content of the queue), in manual mode,
755     // the trainable ones are set to 1 (or 2 for focus).
756     for (int i = 0; i < NUM_SKILLS; ++i)
757     {
758         // skill_trained doesn't work for gnolls, but all existent skills
759         // will be set as enabled here.
760         if (!you.has_mutation(MUT_DISTRIBUTED_TRAINING)
761             && (you.auto_training || !skill_trained(i)))
762         {
763             you.training[i] = 0;
764         }
765         else
766             you.training[i] = you.train[i];
767     }
768 
769     bool empty = true;
770     // In automatic mode, we fill the array with the content of the queue.
771     if (you.auto_training)
772     {
773         for (auto sk : you.exercises)
774         {
775             if (skill_trained(sk))
776             {
777                 you.training[sk] += you.train[sk];
778                 empty = false;
779             }
780         }
781 
782         // We count the practise events in the other queue.
783         FixedVector<unsigned int, NUM_SKILLS> exer_all;
784         exer_all.init(0);
785         for (auto sk : you.exercises_all)
786         {
787             if (skill_trained(sk))
788             {
789                 exer_all[sk] += you.train[sk];
790                 empty = false;
791             }
792         }
793 
794         // We keep the highest of the 2 numbers.
795         for (int sk = 0; sk < NUM_SKILLS; ++sk)
796             you.training[sk] = max(you.training[sk], exer_all[sk]);
797 
798         // The selected skills have not been exercised recently. Give them all
799         // a default weight of 1 (or 2 for focus skills).
800         if (empty)
801         {
802             for (int sk = 0; sk < NUM_SKILLS; ++sk)
803                 if (skill_trained(sk))
804                     you.training[sk] = you.train[sk];
805         }
806 
807         // Focused skills get at least 20% training.
808         for (int sk = 0; sk < NUM_SKILLS; ++sk)
809             if (you.train[sk] == 2 && you.training[sk] < 20
810                 && you.can_currently_train[sk])
811             {
812                 you.training[sk] += 5 * (5 - you.training[sk] / 4);
813             }
814     }
815 
816     _scale_array(you.training, 100, you.auto_training);
817     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
818     {
819         // we use the full set of skills to calculate gnoll percentages,
820         // but they don't actually get to train sacrificed skills.
821         for (int i = 0; i < NUM_SKILLS; ++i)
822             if (is_useless_skill((skill_type) i))
823                 you.training[i] = 0;
824     }
825 }
826 
exercise(skill_type exsk,int deg)827 void exercise(skill_type exsk, int deg)
828 {
829     if (you.skills[exsk] >= MAX_SKILL_LEVEL)
830         return;
831 
832     dprf(DIAG_SKILLS, "Exercise %s by %d.", skill_name(exsk), deg);
833 
834     // push first in case queues are empty, like during -test
835     while (deg > 0)
836     {
837         if (skill_trained(exsk))
838         {
839             you.exercises.push_back(exsk);
840             you.exercises.pop_front();
841         }
842         you.exercises_all.push_back(exsk);
843         you.exercises_all.pop_front();
844         deg--;
845     }
846     reset_training();
847 }
848 
849 // Check if we should stop training this skill immediately.
850 // We look at skill points because actual level up comes later.
_level_up_check(skill_type sk,bool simu)851 static bool _level_up_check(skill_type sk, bool simu)
852 {
853     // Don't train past level 27.
854     if (you.skill_points[sk] >= skill_exp_needed(MAX_SKILL_LEVEL, sk))
855     {
856         you.training[sk] = 0;
857         if (!simu)
858         {
859             you.train[sk] = TRAINING_DISABLED;
860             you.train_alt[sk] = TRAINING_DISABLED;
861         }
862         return true;
863     }
864 
865     return false;
866 }
867 
is_magic_skill(skill_type sk)868 bool is_magic_skill(skill_type sk)
869 {
870     return sk > SK_LAST_MUNDANE && sk <= SK_LAST_MAGIC;
871 }
872 
873 int _gnoll_total_skill_cost();
874 
train_skills(bool simu)875 void train_skills(bool simu)
876 {
877     int cost, exp;
878     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
879     {
880         do
881         {
882             exp = you.exp_available;
883             cost = _gnoll_total_skill_cost();
884             if (exp >= cost)
885             {
886                 _train_skills(exp, calc_skill_cost(you.skill_cost_level), simu);
887                 dprf(DIAG_SKILLS,
888                     "Trained all gnoll skills by 1 at total cost %d.", cost);
889             }
890         }
891         while (exp != you.exp_available);
892     }
893     else
894     {
895         do
896         {
897             cost = calc_skill_cost(you.skill_cost_level);
898             exp = you.exp_available;
899             if (you.skill_cost_level == MAX_SKILL_COST_LEVEL)
900                 _train_skills(exp, cost, simu);
901             else
902             {
903                 // Amount of experience points needed to reach the next skill cost level
904                 const int next_level = skill_cost_needed(you.skill_cost_level + 1)
905                                        - you.total_experience;
906                 ASSERT(next_level > 0);
907                 _train_skills(min(exp, next_level + cost - 1), cost, simu);
908             }
909         }
910         while (you.exp_available >= cost && exp != you.exp_available);
911     }
912 
913     for (int i = 0; i < NUM_SKILLS; ++i)
914         check_skill_level_change(static_cast<skill_type>(i), !simu);
915 
916     // We might have disabled some skills on level up.
917     reset_training();
918 }
919 
920 //#define DEBUG_TRAINING_COST
_train_skills(int exp,const int cost,const bool simu)921 static void _train_skills(int exp, const int cost, const bool simu)
922 {
923     bool skip_first_phase = false;
924     int magic_gain = 0;
925     FixedVector<int, NUM_SKILLS> sk_exp;
926     sk_exp.init(0);
927     vector<skill_type> training_order;
928 #ifdef DEBUG_DIAGNOSTICS
929     FixedVector<int, NUM_SKILLS> total_gain;
930     total_gain.init(0);
931 #endif
932 #ifdef DEBUG_TRAINING_COST
933     int exp_pool = you.exp_available;
934     dprf(DIAG_SKILLS,
935          "skill cost level: %d, cost: %dxp/skp, max XP usable: %d.",
936          you.skill_cost_level, cost, exp);
937 #endif
938 
939     // pre-check training targets -- may disable some skills.
940     if (!simu)
941         check_training_targets();
942 
943     // We scale the training array to the amount of XP available in the pool.
944     // That gives us the amount of XP available to train each skill.
945     for (int i = 0; i < NUM_SKILLS; ++i)
946     {
947         if (you.training[i] > 0)
948         {
949             sk_exp[i] = you.training[i] * exp / 100;
950             if (sk_exp[i] < cost && !you.has_mutation(MUT_DISTRIBUTED_TRAINING))
951             {
952                 // One skill has a too low training to be trained at all.
953                 // We skip the first phase and go directly to the random
954                 // phase so it has a chance to be trained.
955                 skip_first_phase = true;
956                 break;
957             }
958             training_order.push_back(static_cast<skill_type>(i));
959         }
960     }
961 
962     if (!skip_first_phase)
963     {
964         // We randomize the order, to avoid a slight bias to first skills.
965         // Being trained first can make a difference if skill cost increases.
966         if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
967             reverse(training_order.begin(), training_order.end());
968         else
969             shuffle_array(training_order);
970         for (auto sk : training_order)
971         {
972             int gain = 0;
973 
974             if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
975                 sk_exp[sk] = exp;
976             while (sk_exp[sk] >= cost && you.training[sk])
977             {
978                 exp -= sk_exp[sk];
979                 gain += _train(sk, sk_exp[sk], simu);
980                 exp += sk_exp[sk];
981                 ASSERT(exp >= 0);
982                 if (_level_up_check(sk, simu))
983                     sk_exp[sk] = 0;
984                 if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
985                     break;
986             }
987 
988             if (gain && is_magic_skill(sk))
989                 magic_gain += gain;
990 
991 #ifdef DEBUG_DIAGNOSTICS
992            total_gain[sk] += gain;
993 #endif
994         }
995     }
996     // If there's enough xp in the pool, we use it to train skills selected
997     // with random_choose_weighted.
998     while (exp >= cost)
999     {
1000         if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
1001             break;
1002         int gain;
1003         skill_type sk = SK_NONE;
1004         if (!skip_first_phase)
1005             sk = static_cast<skill_type>(random_choose_weighted(sk_exp));
1006         if (is_invalid_skill(sk) || !you.train[sk])
1007             sk = static_cast<skill_type>(random_choose_weighted(you.training));
1008         if (!is_invalid_skill(sk))
1009         {
1010             gain = _train(sk, exp, simu);
1011             ASSERT(exp >= 0);
1012             sk_exp[sk] = 0;
1013         }
1014         else
1015         {
1016             // No skill to train. Can happen if all skills are at 27.
1017             break;
1018         }
1019 
1020         _level_up_check(sk, simu);
1021 
1022         if (gain && is_magic_skill(sk))
1023             magic_gain += gain;
1024 
1025 #ifdef DEBUG_DIAGNOSTICS
1026         total_gain[sk] += gain;
1027 #endif
1028     }
1029 
1030     // clean up any cross-training effects
1031     if (!simu)
1032         check_training_targets();
1033 
1034 #ifdef DEBUG_DIAGNOSTICS
1035     if (!crawl_state.script)
1036     {
1037 #ifdef DEBUG_TRAINING_COST
1038         int total = 0;
1039 #endif
1040         for (int i = 0; i < NUM_SKILLS; ++i)
1041         {
1042             skill_type sk = static_cast<skill_type>(i);
1043             if (total_gain[sk] && !simu
1044                 && !you.has_mutation(MUT_DISTRIBUTED_TRAINING))
1045             {
1046                 dprf(DIAG_SKILLS, "Trained %s by %d.",
1047                      skill_name(sk), total_gain[sk]);
1048             }
1049 #ifdef DEBUG_TRAINING_COST
1050             total += total_gain[sk];
1051         }
1052         dprf(DIAG_SKILLS, "Total skill points gained: %d, cost: %d XP.",
1053              total, exp_pool - you.exp_available);
1054 #else
1055         }
1056 #endif
1057     }
1058 #endif
1059 
1060     // Avoid doubly rewarding spell practise in sprint
1061     // (by inflated XP and inflated piety gain)
1062     if (crawl_state.game_is_sprint())
1063         magic_gain = sprint_modify_exp_inverse(magic_gain);
1064 
1065     if (magic_gain && !simu)
1066         did_god_conduct(DID_SPELL_PRACTISE, div_rand_round(magic_gain, 10));
1067 }
1068 
skill_trained(int i)1069 bool skill_trained(int i)
1070 {
1071     return you.can_currently_train[i] && you.train[i];
1072 }
1073 
1074 /**
1075  * Is the training target, if any, met or exceeded for skill sk?
1076  *
1077  * @param sk the skill to check. This checks crosstraining and ash bonuses,
1078  * but not other skill modifiers.
1079  * @param target the target to check against. Defaults to you.training_targets[sk]
1080  *
1081  * @return whether the skill target has been met.
1082  */
target_met(skill_type sk,unsigned int target)1083 bool target_met(skill_type sk, unsigned int target)
1084 {
1085     return you.skill(sk, 10, false, false) >= (int) target;
1086 }
1087 
target_met(skill_type sk)1088 bool target_met(skill_type sk)
1089 {
1090     return target_met(sk, you.training_targets[sk]);
1091 }
1092 
1093 /**
1094  * Check the training target (if any) for skill sk, and change state
1095  * appropriately. If the target has been met or exceeded, this will turn off
1096  * targeting for that skill, and stop training it. This does *not* reset the
1097  * training percentages, though, so if it's used mid-training, you need to take
1098  * care of that.
1099  *
1100  * @param sk the skill to check.
1101  * @return whether a target was reached.
1102  */
check_training_target(skill_type sk)1103 bool check_training_target(skill_type sk)
1104 {
1105     if (you.training_targets[sk] && target_met(sk))
1106     {
1107         bool base = you.skill(sk, 10, false, false) != you.skill(sk, 10);
1108         mprf("%sraining target %d.%d for %s reached!",
1109             base ? "Base t" : "T",
1110             you.training_targets[sk] / 10,
1111             you.training_targets[sk] % 10, skill_name(sk));
1112 
1113         you.training_targets[sk] = 0;
1114         you.train[sk] = TRAINING_DISABLED;
1115         you.train_alt[sk] = TRAINING_DISABLED;
1116         return true;
1117     }
1118     return false;
1119 }
1120 
1121 /**
1122  * Check the training target (if any) for all skills, and change state
1123  * appropriately.
1124  *
1125  * @return whether any target was reached.
1126  *
1127  * @see check_training_target
1128  */
check_training_targets()1129 bool check_training_targets()
1130 {
1131     bool change = false;
1132     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
1133         change |= check_training_target(sk);
1134     if (change)
1135         reset_training();
1136     return change;
1137 }
1138 
_calc_skill_cost_level(int xp,int start)1139 static int _calc_skill_cost_level(int xp, int start)
1140 {
1141     while (start < MAX_SKILL_COST_LEVEL
1142            && xp >= (int) skill_cost_needed(start + 1))
1143     {
1144         ++start;
1145     }
1146     while (start > 0
1147            && xp < (int) skill_cost_needed(start))
1148     {
1149         --start;
1150     }
1151     return start;
1152 }
1153 
check_skill_cost_change()1154 void check_skill_cost_change()
1155 {
1156 #ifdef DEBUG_TRAINING_COST
1157     int initial_cost = you.skill_cost_level;
1158 #endif
1159 
1160     you.skill_cost_level = _calc_skill_cost_level(you.total_experience, you.skill_cost_level);
1161 
1162 #ifdef DEBUG_TRAINING_COST
1163     if (initial_cost != you.skill_cost_level)
1164         dprf("Adjusting skill cost level to %d", you.skill_cost_level);
1165 #endif
1166 }
1167 
_useless_skill_count()1168 static int _useless_skill_count()
1169 {
1170     int count = 0;
1171     for (skill_type skill = SK_FIRST_SKILL; skill < NUM_SKILLS; ++skill)
1172     {
1173         if (is_removed_skill(skill))
1174             continue;
1175         if (is_useless_skill(skill))
1176             count++;
1177     }
1178     return count;
1179 }
1180 
_total_skill_count()1181 static int _total_skill_count()
1182 {
1183     int count = 0;
1184     for (skill_type skill = SK_FIRST_SKILL; skill < NUM_SKILLS; ++skill)
1185     {
1186         if (is_removed_skill(skill))
1187             continue;
1188         count++;
1189     }
1190     return count;
1191 }
1192 
1193 // The current cost of raising each skill by one skill point, taking the
1194 // gnoll penalty for useless skills into account and rounding up for all
1195 // computations. Used to ensure that gnoll skills rise evenly - we don't
1196 // train anything unless we have this much xp to spend.
_gnoll_total_skill_cost()1197 int _gnoll_total_skill_cost()
1198 {
1199     int this_cost;
1200     int total_cost = 0;
1201     int cur_cost_level = you.skill_cost_level;
1202     const int useless_count = _useless_skill_count();
1203     const int total_count = _total_skill_count();
1204     const int num = total_count;
1205     const int denom = total_count - useless_count;
1206     for (int i = 0; i < NUM_SKILLS; ++i)
1207     {
1208         if (!you.training[i])
1209             continue;
1210         cur_cost_level = _calc_skill_cost_level(you.total_experience + total_cost, cur_cost_level);
1211         this_cost = calc_skill_cost(cur_cost_level);
1212         if (num != denom)
1213             this_cost = (num * this_cost + denom - 1) / denom;
1214         total_cost += this_cost;
1215     }
1216     return total_cost;
1217 }
1218 
change_skill_points(skill_type sk,int points,bool do_level_up)1219 void change_skill_points(skill_type sk, int points, bool do_level_up)
1220 {
1221     if (static_cast<int>(you.skill_points[sk]) < -points)
1222         points = -(int)you.skill_points[sk];
1223 
1224     you.skill_points[sk] += points;
1225 
1226     check_skill_level_change(sk, do_level_up);
1227 }
1228 
1229 // Calculates the skill points required to reach the training target
1230 // Does not currently consider Ashenzari skill boost for experience currently being gained
1231 // so this may still result in some overtraining
_training_target_skill_point_diff(skill_type exsk,int training_target)1232 static int _training_target_skill_point_diff(skill_type exsk, int training_target)
1233 {
1234     int target_level = training_target / 10;
1235     int target_fractional = training_target % 10;
1236     int target_skill_points;
1237 
1238     if (target_level == MAX_SKILL_LEVEL)
1239         target_skill_points = skill_exp_needed(target_level, exsk);
1240     else
1241     {
1242         int target_level_points = skill_exp_needed(target_level, exsk);
1243         int target_next_level_points = skill_exp_needed(target_level + 1, exsk);
1244         // Round up for any remainder to ensure target is hit
1245         target_skill_points = target_level_points
1246             + div_round_up((target_next_level_points - target_level_points)
1247                             * target_fractional, 10);
1248     }
1249 
1250     int you_skill_points = you.skill_points[exsk] + get_crosstrain_points(exsk);
1251     if (ash_has_skill_boost(exsk))
1252         you_skill_points += ash_skill_point_boost(exsk, training_target);
1253 
1254     int target_skill_point_diff = target_skill_points - you_skill_points;
1255 
1256     int manual_charges = you.skill_manual_points[exsk];
1257     if (manual_charges > 0)
1258         target_skill_point_diff -= min(manual_charges, target_skill_point_diff / 2);
1259 
1260     return target_skill_point_diff;
1261 }
1262 
_train(skill_type exsk,int & max_exp,bool simu)1263 static int _train(skill_type exsk, int &max_exp, bool simu)
1264 {
1265     // This will be added to you.skill_points[exsk];
1266     int skill_inc = 1;
1267 
1268     // This will be deducted from you.exp_available.
1269     int cost = calc_skill_cost(you.skill_cost_level);
1270 
1271     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
1272     {
1273         int useless_count = _useless_skill_count();
1274         int total_count = _total_skill_count();
1275         int num = total_count;
1276         int denom = total_count - useless_count;
1277         if (num != denom)
1278             cost = div_rand_round(num * cost, denom);
1279     }
1280     else
1281     {
1282         // Scale cost and skill_inc to available experience.
1283         const int spending_limit = min(10 * MAX_SPENDING_LIMIT, max_exp);
1284         skill_inc = spending_limit / cost;
1285 
1286         int training_target = you.training_targets[exsk];
1287         if (training_target > you.skill(exsk, 10, false, false))
1288         {
1289             int target_skill_point_diff = _training_target_skill_point_diff(exsk, training_target);
1290             if (target_skill_point_diff > 0)
1291                 skill_inc = min(skill_inc, target_skill_point_diff);
1292         }
1293         cost = skill_inc * cost;
1294     }
1295 
1296     if (skill_inc <= 0 || cost > max_exp)
1297         return 0;
1298 
1299     // Bonus from manual
1300     int bonus_left = skill_inc;
1301     if (you.skill_manual_points[exsk])
1302     {
1303         const int bonus = min<int>(bonus_left, you.skill_manual_points[exsk]);
1304         skill_inc += bonus;
1305         bonus_left -= bonus;
1306         you.skill_manual_points[exsk] -= bonus;
1307         if (!you.skill_manual_points[exsk] && !simu && !crawl_state.simulating_xp_gain)
1308         {
1309             mprf("You have finished your manual of %s and toss it away.",
1310                  skill_name(exsk));
1311         }
1312     }
1313 
1314     const skill_type old_best_skill = best_skill(SK_FIRST_SKILL, SK_LAST_SKILL);
1315     const int old_level = you.skill(exsk, 10, true);
1316     you.skill_points[exsk] += skill_inc;
1317     you.exp_available -= cost;
1318     you.total_experience += cost;
1319     max_exp -= cost;
1320 
1321     if (!simu)
1322     {
1323         check_training_targets();
1324         redraw_skill(exsk, old_best_skill, (you.skill(exsk, 10, true) > old_level));
1325     }
1326 
1327     check_skill_cost_change();
1328     ASSERT(you.exp_available >= 0);
1329     ASSERT(max_exp >= 0);
1330     you.redraw_experience = true;
1331 
1332     return skill_inc;
1333 }
1334 
1335 /**
1336  * Calculate the difference in skill points and xp for new skill level `amount`.
1337  *
1338  * If `base_only` is false, this will produce an estimate only. Otherwise, it is
1339  * exact. This function is used in both estimating skill targets, and in
1340  * actually changing skills. It will work both for cases where the new skill
1341  * level is greater, and where it is less, than the current level.
1342  *
1343  * @param skill the skill to calculate for.
1344  * @param amount the new skill level for `skill`.
1345  * @param scaled_training how to scale the training values (for estimating what
1346  *          happens when other skills are being trained).
1347  * @param base_only whether to calculate on actual unmodified skill levels, or
1348  *          use various bonuses that apply on top of these (crosstraining, ash,
1349  *          manuals).
1350  *
1351  * @return a pair consisting of the difference in skill points, and the
1352  *          difference in xp.
1353  */
skill_level_to_diffs(skill_type skill,double amount,int scaled_training,bool base_only)1354 skill_diff skill_level_to_diffs(skill_type skill, double amount,
1355                             int scaled_training,
1356                             bool base_only)
1357 {
1358     // TODO: should this use skill_state?
1359     // TODO: can `amount` be converted to fixed point?
1360     double level;
1361     double fractional = modf(amount, &level);
1362     if (level >= MAX_SKILL_LEVEL)
1363     {
1364         level = MAX_SKILL_LEVEL;
1365         fractional = 0;
1366     }
1367 
1368     unsigned int target = skill_exp_needed(level, skill);
1369     if (fractional)
1370     {
1371         target += (skill_exp_needed(level + 1, skill)
1372                   - skill_exp_needed(level, skill)) * fractional + 1;
1373     }
1374 
1375     // We're calculating you.skill_points[skill] and calculating the new
1376     // you.total_experience to update skill cost.
1377 
1378     unsigned int you_skill = you.skill_points[skill];
1379 
1380     if (!base_only)
1381     {
1382         // Factor in crosstraining bonus at the time of the query.
1383         // This will not address the case where some cross-training skills are
1384         // also being trained.
1385         you_skill += get_crosstrain_points(skill);
1386 
1387         // Estimate the ash bonus, based on current skill levels and piety.
1388         // This isn't perfectly accurate, because the boost changes as
1389         // skill increases. TODO: exact solution.
1390         // It also assumes that piety won't change.
1391         if (ash_has_skill_boost(skill))
1392             you_skill += ash_skill_point_boost(skill, you.skills[skill] * 10);
1393 
1394         if (you.skill_manual_points[skill])
1395             target = you_skill + (target - you_skill) / 2;
1396     }
1397 
1398     if (target == you_skill)
1399         return skill_diff();
1400 
1401     // Do we need to increase or decrease skill points/xp?
1402     // XXX: reducing with ash bonuses in play could lead to weird results.
1403     const bool decrease_skill = target < you_skill;
1404 
1405     int you_xp = you.total_experience;
1406     int you_skill_cost_level = you.skill_cost_level;
1407 
1408 #ifdef DEBUG_TRAINING_COST
1409     dprf(DIAG_SKILLS, "target skill points: %d.", target);
1410 #endif
1411     while (you_skill != target)
1412     {
1413         // each loop is the max skill points that can be gained at the
1414         // current skill cost level, up to `target`.
1415 
1416         // If we are decreasing, find the xp needed to get to the current skill
1417         // cost level. Otherwise, find the xp needed to get to the next one.
1418         const int next_level = skill_cost_needed(you_skill_cost_level +
1419                                                     (decrease_skill ? 0 : 1));
1420 
1421         // max xp that can be added (or subtracted) in one pass of the loop
1422         int max_xp = abs(next_level - you_xp);
1423 
1424         // When reducing, we don't want to stop right at the limit, unless
1425         // we're at skill cost level 0.
1426         if (decrease_skill && you_skill_cost_level)
1427             ++max_xp;
1428 
1429         const int cost = calc_skill_cost(you_skill_cost_level);
1430         // Maximum number of skill points to transfer in one go.
1431         // It's max_xp/cost rounded up.
1432         const int max_skp = max((max_xp + cost - 1) / cost, 1);
1433 
1434         skill_diff delta;
1435         delta.skill_points = min<int>(abs((int)(target - you_skill)),
1436                                  max_skp);
1437         delta.experience = delta.skill_points * cost;
1438 
1439         if (decrease_skill)
1440         {
1441             // We are decreasing skill points / xp to reach the target. Ensure
1442             // that the delta is negative but won't result in negative skp or xp
1443             delta.skill_points = -min<int>(delta.skill_points, you_skill);
1444             delta.experience = -min<int>(delta.experience, you_xp);
1445         }
1446 
1447 #ifdef DEBUG_TRAINING_COST
1448         dprf(DIAG_SKILLS, "cost level: %d, total experience: %d, "
1449              "next level: %d, skill points: %d, delta_skp: %d, delta_xp: %d.",
1450              you_skill_cost_level, you_xp, next_level,
1451              you_skill, delta.skill_points, delta.experience);
1452 #endif
1453         you_skill += (delta.skill_points * scaled_training
1454                                         + (decrease_skill ? -99 : 99)) / 100;
1455         you_xp += delta.experience;
1456         you_skill_cost_level = _calc_skill_cost_level(you_xp, you_skill_cost_level);
1457     }
1458 
1459     return skill_diff(you_skill - you.skill_points[skill],
1460                                 you_xp - you.total_experience);
1461 }
1462 
set_skill_level(skill_type skill,double amount)1463 void set_skill_level(skill_type skill, double amount)
1464 {
1465     double level;
1466     modf(amount, &level);
1467 
1468     you.ct_skill_points[skill] = 0;
1469 
1470     skill_diff diffs = skill_level_to_diffs(skill, amount);
1471 
1472     you.skills[skill] = level;
1473     you.skill_points[skill] += diffs.skill_points;
1474     you.total_experience += diffs.experience;
1475 #ifdef DEBUG_TRAINING_COST
1476     dprf("Change (total): %d skp (%d), %d xp (%d)",
1477         diffs.skill_points, you.skill_points[skill],
1478         diffs.experience, you.total_experience);
1479 #endif
1480 
1481     check_skill_cost_change();
1482 
1483     // need to check them all, to handle crosstraining.
1484     check_training_targets();
1485 }
1486 
get_skill_progress(skill_type sk,int level,int points,int scale)1487 int get_skill_progress(skill_type sk, int level, int points, int scale)
1488 {
1489     if (level >= MAX_SKILL_LEVEL)
1490         return 0;
1491 
1492     const int needed = skill_exp_needed(level + 1, sk);
1493     const int prev_needed = skill_exp_needed(level, sk);
1494     if (needed == 0) // invalid race, legitimate at startup
1495         return 0;
1496     // A scale as small as 92 would overflow with 31 bits if skill_rdiv()
1497     // is involved: needed can be 91985, skill_rdiv() multiplies by 256.
1498     const int64_t amt_done = points - prev_needed;
1499     int prog = amt_done * scale / (needed - prev_needed);
1500 
1501     ASSERT(prog >= 0);
1502 
1503     return prog;
1504 }
1505 
get_skill_progress(skill_type sk,int scale)1506 int get_skill_progress(skill_type sk, int scale)
1507 {
1508     return get_skill_progress(sk, you.skills[sk], you.skill_points[sk], scale);
1509 }
1510 
get_skill_percentage(const skill_type x)1511 int get_skill_percentage(const skill_type x)
1512 {
1513     return get_skill_progress(x, 100);
1514 }
1515 
1516 /**
1517  * Get the training target for a skill.
1518  *
1519  * @param sk the skill to set
1520  * @return the current target, scaled by 10 -- so between 0 and 270.
1521  *         0 means no target.
1522  */
get_training_target(const skill_type sk) const1523 int player::get_training_target(const skill_type sk) const
1524 {
1525     ASSERT_LESS(training_targets[sk], 271);
1526     return training_targets[sk];
1527 }
1528 
1529 /**
1530  * Set the training target for a skill.
1531  *
1532  * @param sk the skill to set
1533  * @param target the new target, between 0.0 and 27.0.  0.0 means no target.
1534  */
set_training_target(const skill_type sk,const double target,bool announce)1535 bool player::set_training_target(const skill_type sk, const double target, bool announce)
1536 {
1537     return set_training_target(sk, (int) round(target * 10), announce);
1538 }
1539 
clear_training_targets()1540 void player::clear_training_targets()
1541 {
1542     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
1543         set_training_target(sk, 0);
1544 }
1545 
1546 /**
1547  * Set the training target for a skill, scaled by 10.
1548  *
1549  * @param sk the skill to set
1550  * @param target the new target, scaled by ten, so between 0 and 270.  0 means
1551  *               no target.
1552  *
1553  * @return whether setting the target succeeded.
1554  */
set_training_target(const skill_type sk,const int target,bool announce)1555 bool player::set_training_target(const skill_type sk, const int target, bool announce)
1556 {
1557     const int ranged_target = min(max((int) target, 0), 270);
1558     if (announce && ranged_target != (int) training_targets[sk])
1559     {
1560         if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
1561             mprf("You can't set training targets!");
1562         else if (ranged_target == 0)
1563             mprf("Clearing the skill training target for %s.", skill_name(sk));
1564         else
1565         {
1566             mprf("Setting a skill training target for %s at %d.%d.", skill_name(sk),
1567                                     ranged_target / 10, ranged_target % 10);
1568         }
1569     }
1570     if (!can_enable_skill(sk)) // checks for gnolls
1571     {
1572         training_targets[sk] = 0;
1573         return false;
1574     }
1575     training_targets[sk] = ranged_target;
1576     return true;
1577 }
1578 
skill_name(skill_type which_skill)1579 const char *skill_name(skill_type which_skill)
1580 {
1581     return skill_titles[which_skill][0];
1582 }
1583 
skill_abbr(skill_type which_skill)1584 const char * skill_abbr(skill_type which_skill)
1585 {
1586     return skill_titles[which_skill][6];
1587 }
1588 
1589 /**
1590  * Get a skill_type from an (exact, case-insensitive) skill name.
1591  *
1592  * @return a valid skill_type, or SK_NONE on failure.
1593  *
1594  * @see skill_from_name for a non-exact version.
1595  */
str_to_skill(const string & skill)1596 skill_type str_to_skill(const string &skill)
1597 {
1598     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
1599         if (lowercase_string(skill) == lowercase_string(skill_titles[sk][0]))
1600             return sk;
1601 
1602     return SK_NONE;
1603 }
1604 
1605 /**
1606  * Get a skill_type from a skill name.
1607  *
1608  * @return a valid skill_type, or SK_FIGHTING on failure.
1609  */
str_to_skill_safe(const string & skill)1610 skill_type str_to_skill_safe(const string &skill)
1611 {
1612     // legacy behaviour -- ensure that a valid skill is returned.
1613     skill_type sk = str_to_skill(skill);
1614     if (sk == SK_NONE)
1615         return SK_FIGHTING;
1616     else
1617         return sk;
1618 }
1619 
_stk_weight(species_type species)1620 static string _stk_weight(species_type species)
1621 {
1622     if (species::size(species) == SIZE_LARGE)
1623         return "Heavy";
1624     else if (species::size(species, PSIZE_BODY) == SIZE_LARGE)
1625         return "Cruiser";
1626     else if (species::size(species) == SIZE_SMALL || species == SP_TENGU)
1627         return "Feather";
1628     else if (species::size(species) == SIZE_LITTLE)
1629         return "Fly";
1630     else if (species::is_elven(species))
1631         return "Light";
1632     else
1633         return "Middle";
1634 }
1635 
get_skill_rank(unsigned skill_lev)1636 unsigned get_skill_rank(unsigned skill_lev)
1637 {
1638     // Translate skill level into skill ranking {dlb}:
1639     return (skill_lev <= 7)  ? 0 :
1640                            (skill_lev <= 14) ? 1 :
1641                            (skill_lev <= 20) ? 2 :
1642                            (skill_lev <= 26) ? 3
1643                            /* level 27 */    : 4;
1644 }
1645 
1646 // XX should at least some of this be in species.cc?
1647 
1648 /**
1649  * What title will the player get at the given rank of the given skill?
1650  *
1651  * @param best_skill    The skill used to determine the title.
1652  * @param skill_rank    The player's rank in the given skill.
1653  * @param species       The player's species.
1654  * @param dex_better    Whether the player's dexterity is higher than strength.
1655  * @param god           The god_type of the god the player follows.
1656  * @param piety         The player's piety with the given god.
1657  * @return              An appropriate and/or humorous title.
1658  */
skill_title_by_rank(skill_type best_skill,uint8_t skill_rank,species_type species,bool dex_better,god_type god,int piety)1659 string skill_title_by_rank(skill_type best_skill, uint8_t skill_rank,
1660                            species_type species, bool dex_better,
1661                            god_type god, int piety)
1662 {
1663 
1664     // paranoia
1665     if (is_invalid_skill(best_skill))
1666         return "Adventurer";
1667 
1668     // Increment rank by one to "skip" skill name in array {dlb}:
1669     ++skill_rank;
1670 
1671     string result;
1672 
1673     if (best_skill < NUM_SKILLS)
1674     {
1675         switch (best_skill)
1676         {
1677         case SK_SUMMONINGS:
1678             // retro goody-bag for decidedly non-goodies
1679             if (is_evil_god(god))
1680             {
1681                 if (skill_rank == 4)
1682                     result = "Demonologist";
1683                 else if (skill_rank == 5)
1684                     result = "Hellbinder";
1685             }
1686             break;
1687 
1688         case SK_POLEARMS:
1689             if (species == SP_PALENTONGA && skill_rank == 5)
1690                 result = "Prickly Pangolin";
1691             break;
1692 
1693         case SK_UNARMED_COMBAT:
1694             if (species == SP_FELID)
1695                 result = claw_and_tooth_titles[skill_rank];
1696             else if (species == SP_MUMMY && skill_rank == 5)
1697                 result = "Pharaoh";
1698             else if (!dex_better && species == SP_DJINNI && skill_rank == 5)
1699                 result = "Weightless Champion";
1700             else
1701             {
1702                 result = dex_better ? martial_arts_titles[skill_rank]
1703                                     : skill_titles[best_skill][skill_rank];
1704             }
1705             break;
1706 
1707         case SK_SHORT_BLADES:
1708             if (species::is_elven(species) && skill_rank == 5)
1709             {
1710                 result = "Blademaster";
1711                 break;
1712             }
1713             break;
1714 
1715         case SK_LONG_BLADES:
1716             if (species == SP_MERFOLK && skill_rank == 5)
1717             {
1718                 result = "Swordfish";
1719                 break;
1720             }
1721             break;
1722 
1723         case SK_INVOCATIONS:
1724             if (species == SP_DEMONSPAWN && skill_rank == 5 && is_evil_god(god))
1725                 result = "Blood Saint";
1726             else if (species == SP_PALENTONGA && skill_rank == 5 && god == GOD_QAZLAL)
1727                 result = "Rolling Thunder";
1728             else if (species == SP_PALENTONGA && skill_rank == 5 && is_good_god(god))
1729                 result = "Holy Roller";
1730             else if (species == SP_MUMMY && skill_rank == 5 && god == GOD_NEMELEX_XOBEH)
1731                 result = "Forbidden One";
1732             else if (species == SP_VINE_STALKER && skill_rank == 5 && god == GOD_NEMELEX_XOBEH)
1733                 result = "Black Lotus";
1734             else if (species == SP_GARGOYLE && skill_rank == 5 && god == GOD_JIYVA)
1735                 result = "Rockslime";
1736             else if (god != GOD_NO_GOD)
1737                 result = god_title(god, species, piety);
1738             else if (species == SP_BARACHI)
1739             {
1740                 // C.f. the barachi species lore, true believers!
1741                 result = "God-Hated";
1742             }
1743             break;
1744 
1745         case SK_BOWS:
1746             if (species::is_elven(species) && skill_rank == 5)
1747             {
1748                 result = "Master Archer";
1749                 break;
1750             }
1751             break;
1752 
1753         case SK_SPELLCASTING:
1754             if (species == SP_OGRE)
1755                 result = "Ogre Mage";
1756             break;
1757 
1758         case SK_CONJURATIONS:
1759             // Stay safe, Winslem :(
1760             if (species == SP_TROLL && skill_rank > 3)
1761                 result = "Wallbreaker";
1762             break;
1763 
1764         // For the below draconian titles, intentionally don't restrict
1765         // by drac colour to avoid frustrating players trying for these
1766         case SK_FIRE_MAGIC:
1767             if (species::is_draconian(species) && skill_rank == 5)
1768                 result = "Fire Dragon";
1769             else if (species == SP_MUMMY && skill_rank == 5)
1770                 result = "Highly Combustible";
1771             break;
1772 
1773         case SK_ICE_MAGIC:
1774             if (species::is_draconian(species) && skill_rank == 5)
1775                 result = "Ice Dragon";
1776             break;
1777 
1778         case SK_EARTH_MAGIC:
1779             if (species::is_draconian(species) && skill_rank == 5)
1780                 result = "Iron Dragon";
1781             break;
1782 
1783         case SK_AIR_MAGIC:
1784             if (species::is_draconian(species) && skill_rank == 5)
1785                 result = "Storm Dragon";
1786             break;
1787 
1788         case SK_POISON_MAGIC:
1789             if (species::is_draconian(species) && skill_rank == 5)
1790                 result = "Swamp Dragon";
1791             break;
1792 
1793         case SK_HEXES:
1794             if (species::is_draconian(species) && skill_rank == 5)
1795                 result = "Faerie Dragon";
1796             break;
1797 
1798         case SK_TRANSLOCATIONS:
1799             if (species == SP_FORMICID && skill_rank == 5)
1800                 result = "Teletunneler";
1801             break;
1802 
1803         case SK_NECROMANCY:
1804             if (species == SP_SPRIGGAN && skill_rank == 5)
1805                 result = "Petite Mort";
1806             else if (species == SP_VINE_STALKER && skill_rank == 5)
1807                 result = "Corpseflower";
1808             else if (god == GOD_KIKUBAAQUDGHA)
1809                 result = god_title(god, species, piety);
1810             break;
1811 
1812 #if TAG_MAJOR_VERSION == 34
1813         case SK_EVOCATIONS:
1814             if (god == GOD_PAKELLAS)
1815                 result = god_title(god, species, piety);
1816             break;
1817 #endif
1818 
1819         default:
1820             break;
1821         }
1822         if (result.empty())
1823             result = skill_titles[best_skill][skill_rank];
1824     }
1825 
1826     const map<string, string> replacements =
1827     {
1828         { "Adj", species::name(species, species::SPNAME_ADJ) },
1829         { "Genus", species::name(species, species::SPNAME_GENUS) },
1830         { "genus", lowercase_string(species::name(species, species::SPNAME_GENUS)) },
1831         { "Genus_Short", species == SP_DEMIGOD ? "God" :
1832                            species::name(species, species::SPNAME_GENUS) },
1833         { "Walker", species::walking_verb(species) + "er" },
1834         { "Weight", _stk_weight(species) },
1835     };
1836 
1837     return replace_keys(result, replacements);
1838 }
1839 
1840 /** What is the player's current title.
1841  *
1842  *  @param the whether to prepend a definite article.
1843  *  @returns the title.
1844  */
player_title(bool the)1845 string player_title(bool the)
1846 {
1847     const skill_type best = best_skill(SK_FIRST_SKILL, SK_LAST_SKILL);
1848     const string title =
1849             skill_title_by_rank(best, get_skill_rank(you.skills[best]));
1850     const string article = !the ? "" : title == "Petite Mort" ? "La " : "the ";
1851     return article + title;
1852 }
1853 
best_skill(skill_type min_skill,skill_type max_skill,skill_type excl_skill)1854 skill_type best_skill(skill_type min_skill, skill_type max_skill,
1855                       skill_type excl_skill)
1856 {
1857     ASSERT(min_skill < NUM_SKILLS);
1858     ASSERT(max_skill < NUM_SKILLS);
1859     skill_type ret = SK_FIGHTING;
1860     unsigned int best_skill_level = 0;
1861     unsigned int best_position = 1000;
1862 
1863     for (int i = min_skill; i <= max_skill; i++)
1864     {
1865         skill_type sk = static_cast<skill_type>(i);
1866         if (sk == excl_skill)
1867             continue;
1868 
1869         const unsigned int skill_level = you.skill(sk, 10, true);
1870         if (skill_level > best_skill_level)
1871         {
1872             ret = sk;
1873             best_skill_level = skill_level;
1874             best_position = you.skill_order[sk];
1875 
1876         }
1877         else if (skill_level == best_skill_level
1878                  && you.skill_order[sk] < best_position)
1879         {
1880             ret = sk;
1881             best_position = you.skill_order[sk];
1882         }
1883     }
1884 
1885     return ret;
1886 }
1887 
1888 // Calculate the skill_order array from scratch.
1889 //
1890 // The skill order array is used for breaking ties in best_skill.
1891 // This is done by ranking each skill by the order in which it
1892 // has attained its current level (the values are the number of
1893 // skills at or above that level when the current skill reached it).
1894 //
1895 // In this way, the skill which has been at a level for the longest
1896 // is judged to be the best skill (thus, nicknames are sticky)...
1897 // other skills will have to attain the next level higher to be
1898 // considered a better skill (thus, the first skill to reach level 27
1899 // becomes the characters final nickname). -- bwr
init_skill_order()1900 void init_skill_order()
1901 {
1902     for (skill_type si = SK_FIRST_SKILL; si < NUM_SKILLS; ++si)
1903     {
1904         const unsigned int i_points = you.skill_points[si]
1905                                       / species_apt_factor(si);
1906 
1907         you.skill_order[si] = 0;
1908 
1909         for (skill_type sj = SK_FIRST_SKILL; sj < NUM_SKILLS; ++sj)
1910         {
1911             if (si == sj)
1912                 continue;
1913 
1914             const unsigned int j_points = you.skill_points[sj]
1915                                           / species_apt_factor(sj);
1916 
1917             if (you.skills[sj] == you.skills[si]
1918                 && (j_points > i_points
1919                     || (j_points == i_points && sj > si)))
1920             {
1921                 you.skill_order[si]++;
1922             }
1923         }
1924     }
1925 }
1926 
is_removed_skill(skill_type skill)1927 bool is_removed_skill(skill_type skill)
1928 {
1929 #if TAG_MAJOR_VERSION == 34
1930     if (skill == SK_STABBING || skill == SK_TRAPS || skill == SK_CHARMS)
1931         return true;
1932 #else
1933     UNUSED(skill);
1934 #endif
1935     return false;
1936 }
1937 
1938 static map<skill_type, mutation_type> skill_sac_muts = {
1939     { SK_AIR_MAGIC,      MUT_NO_AIR_MAGIC },
1940     { SK_FIRE_MAGIC,     MUT_NO_FIRE_MAGIC },
1941     { SK_EARTH_MAGIC,    MUT_NO_EARTH_MAGIC },
1942     { SK_ICE_MAGIC,      MUT_NO_ICE_MAGIC },
1943     { SK_POISON_MAGIC,   MUT_NO_POISON_MAGIC },
1944     { SK_HEXES,          MUT_NO_HEXES_MAGIC },
1945     { SK_TRANSLOCATIONS, MUT_NO_TRANSLOCATION_MAGIC },
1946     { SK_TRANSMUTATIONS, MUT_NO_TRANSMUTATION_MAGIC },
1947     { SK_CONJURATIONS,   MUT_NO_CONJURATION_MAGIC },
1948     { SK_NECROMANCY,     MUT_NO_NECROMANCY_MAGIC },
1949     { SK_SUMMONINGS,     MUT_NO_SUMMONING_MAGIC },
1950 
1951     { SK_DODGING,        MUT_NO_DODGING },
1952     { SK_ARMOUR,         MUT_NO_ARMOUR_SKILL },
1953     { SK_EVOCATIONS,     MUT_NO_ARTIFICE },
1954     { SK_STEALTH,        MUT_NO_STEALTH },
1955 };
1956 
can_sacrifice_skill(mutation_type mut)1957 bool can_sacrifice_skill(mutation_type mut)
1958 {
1959     for (auto sac : skill_sac_muts)
1960         if (sac.second == mut)
1961             return species_apt(sac.first) != UNUSABLE_SKILL;
1962     return true;
1963 }
1964 
is_useless_skill(skill_type skill)1965 bool is_useless_skill(skill_type skill)
1966 {
1967     if (is_removed_skill(skill))
1968         return true;
1969     auto mut = skill_sac_muts.find(skill);
1970     if (mut != skill_sac_muts.end() && you.has_mutation(mut->second))
1971         return true;
1972     // shields isn't in the big map because shields being useless doesn't
1973     // imply that missing hand is meaningless.
1974     if (skill == SK_SHIELDS && you.get_mutation_level(MUT_MISSING_HAND)
1975         || skill == SK_BOWS && you.get_mutation_level(MUT_MISSING_HAND)
1976                             && !you.has_innate_mutation(MUT_QUADRUMANOUS)
1977     )
1978     {
1979         return true;
1980     }
1981 
1982     return species_apt(skill) == UNUSABLE_SKILL;
1983 }
1984 
is_harmful_skill(skill_type skill)1985 bool is_harmful_skill(skill_type skill)
1986 {
1987     return is_magic_skill(skill) && you_worship(GOD_TROG);
1988 }
1989 
1990 /**
1991  * Does the player have trainable skills?
1992  *
1993  * @param check_all If true, also consider skills that are harmful and/or
1994  *        currently untrainable. Useless skills are never considered.
1995  *        Defaults to false.
1996  */
trainable_skills(bool check_all)1997 bool trainable_skills(bool check_all)
1998 {
1999     for (skill_type i = SK_FIRST_SKILL; i < NUM_SKILLS; ++i)
2000     {
2001         skill_type sk = static_cast<skill_type>(i);
2002         if (can_enable_skill(sk, check_all))
2003             return true;
2004     }
2005 
2006     return false;
2007 }
2008 
skill_bump(skill_type skill,int scale)2009 int skill_bump(skill_type skill, int scale)
2010 {
2011     const int sk = you.skill_rdiv(skill, scale);
2012     return sk + min(sk, 3 * scale);
2013 }
2014 
2015 // What aptitude value corresponds to doubled skill learning
2016 // (i.e., old-style aptitude 50).
2017 #define APT_DOUBLE 4
2018 
apt_to_factor(int apt)2019 float apt_to_factor(int apt)
2020 {
2021     return 1 / exp(log(2) * apt / APT_DOUBLE);
2022 }
2023 
skill_exp_needed(int lev,skill_type sk,species_type sp)2024 unsigned int skill_exp_needed(int lev, skill_type sk, species_type sp)
2025 {
2026     const int exp[28] =
2027           { 0, 50, 150, 300, 500, 750,          // 0-5
2028             1050, 1400, 1800, 2250, 2800,       // 6-10
2029             3450, 4200, 5050, 6000, 7050,       // 11-15
2030             8200, 9450, 10800, 12300, 13950,    // 16-20
2031             15750, 17700, 19800, 22050, 24450,  // 21-25
2032             27000, 29750 };
2033 
2034     ASSERT_RANGE(lev, 0, MAX_SKILL_LEVEL + 1);
2035     return exp[lev] * species_apt_factor(sk, sp);
2036 }
2037 
species_apt(skill_type skill,species_type species)2038 int species_apt(skill_type skill, species_type species)
2039 {
2040     static bool spec_skills_initialised = false;
2041     if (!spec_skills_initialised)
2042     {
2043         // Setup sentinel values to find errors more easily.
2044         const int sentinel = -20; // this gives cost 3200
2045         for (int sp = 0; sp < NUM_SPECIES; ++sp)
2046             for (int sk = 0; sk < NUM_SKILLS; ++sk)
2047                 _spec_skills[sp][sk] = sentinel;
2048         for (const species_skill_aptitude &ssa : species_skill_aptitudes)
2049         {
2050             ASSERT(_spec_skills[ssa.species][ssa.skill] == sentinel);
2051             _spec_skills[ssa.species][ssa.skill] = ssa.aptitude;
2052         }
2053         spec_skills_initialised = true;
2054     }
2055 
2056     return max(UNUSABLE_SKILL, _spec_skills[species][skill]
2057                                - you.get_mutation_level(MUT_UNSKILLED));
2058 }
2059 
species_apt_factor(skill_type sk,species_type sp)2060 float species_apt_factor(skill_type sk, species_type sp)
2061 {
2062     return apt_to_factor(species_apt(sk, sp));
2063 }
2064 
get_crosstrain_skills(skill_type sk)2065 vector<skill_type> get_crosstrain_skills(skill_type sk)
2066 {
2067     // Gnolls do not have crosstraining.
2068     if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
2069         return {};
2070 
2071     switch (sk)
2072     {
2073     case SK_SHORT_BLADES:
2074         return { SK_LONG_BLADES };
2075     case SK_LONG_BLADES:
2076         return { SK_SHORT_BLADES };
2077     case SK_AXES:
2078     case SK_STAVES:
2079         return { SK_POLEARMS, SK_MACES_FLAILS };
2080     case SK_MACES_FLAILS:
2081     case SK_POLEARMS:
2082         return { SK_AXES, SK_STAVES };
2083     case SK_SLINGS:
2084         return { SK_THROWING };
2085     case SK_THROWING:
2086         return { SK_SLINGS };
2087     default:
2088         return {};
2089     }
2090 }
2091 
2092 /**
2093  * Calculate the current crosstraining bonus for skill `sk`, in skill points.
2094  */
get_crosstrain_points(skill_type sk)2095 int get_crosstrain_points(skill_type sk)
2096 {
2097     int points = 0;
2098     for (skill_type cross : get_crosstrain_skills(sk))
2099         points += you.skill_points[cross] * 2 / 5;
2100     return points;
2101 
2102 }
2103 
2104 /**
2105  * Is the provided skill one of the elemental spellschools?
2106  *
2107  * @param sk    The skill in question.
2108  * @return      Whether it is fire, ice, earth, or air.
2109  */
_skill_is_elemental(skill_type sk)2110 static bool _skill_is_elemental(skill_type sk)
2111 {
2112     return sk == SK_FIRE_MAGIC || sk == SK_EARTH_MAGIC
2113            || sk == SK_AIR_MAGIC || sk == SK_ICE_MAGIC;
2114 }
2115 
2116 /**
2117  * How skilled is the player at the elemental components of a spell?
2118  *
2119  * @param spell     The type of spell in question.
2120  * @param scale     Scaling factor for skill.
2121  * @return          The player's skill at the elemental parts of a given spell.
2122  */
elemental_preference(spell_type spell,int scale)2123 int elemental_preference(spell_type spell, int scale)
2124 {
2125     skill_set skill_list;
2126     spell_skills(spell, skill_list);
2127     int preference = 0;
2128     for (skill_type sk : skill_list)
2129         if (_skill_is_elemental(sk))
2130             preference += you.skill(sk, scale);
2131     return preference;
2132 }
2133 
2134 /**
2135  * Compare skill levels
2136  *
2137  * It compares the level of 2 skills, and breaks ties by using skill order.
2138  *
2139  * @param sk1 First skill.
2140  * @param sk2 Second skill.
2141  * @return Whether first skill is higher than second skill.
2142  */
compare_skills(skill_type sk1,skill_type sk2)2143 bool compare_skills(skill_type sk1, skill_type sk2)
2144 {
2145     if (is_invalid_skill(sk1))
2146         return false;
2147     else if (is_invalid_skill(sk2))
2148         return true;
2149     else
2150         return you.skill(sk1, 10, true) > you.skill(sk2, 10, true)
2151                || you.skill(sk1, 10, true) == you.skill(sk2, 10, true)
2152                   && you.skill_order[sk1] < you.skill_order[sk2];
2153 }
2154 
dump_skills(string & text)2155 void dump_skills(string &text)
2156 {
2157     for (uint8_t i = 0; i < NUM_SKILLS; i++)
2158     {
2159         int real = you.skill((skill_type)i, 10, true);
2160         int cur  = you.skill((skill_type)i, 10);
2161         if (real > 0 || (!you.auto_training && you.train[i] > 0))
2162         {
2163             text += make_stringf(" %c Level %.*f%s %s\n",
2164                                  real == 270       ? 'O' :
2165                                  !you.can_currently_train[i] ? ' ' :
2166                                  you.train[i] == 2 ? '*' :
2167                                  you.train[i]      ? '+' :
2168                                                      '-',
2169                                  real == 270 ? 0 : 1,
2170                                  real * 0.1,
2171                                  real != cur
2172                                      ? make_stringf("(%.*f)",
2173                                            cur == 270 ? 0 : 1,
2174                                            cur * 0.1).c_str()
2175                                      : "",
2176                                  skill_name(static_cast<skill_type>(i)));
2177         }
2178     }
2179 }
2180 
skill_state()2181 skill_state::skill_state() :
2182         skill_cost_level(0), total_experience(0), auto_training(true),
2183         exp_available(0), saved(false)
2184 {
2185 }
2186 
save()2187 void skill_state::save()
2188 {
2189     can_currently_train = you.can_currently_train;
2190     skills              = you.skills;
2191     train               = you.train;
2192     training            = you.training;
2193     skill_points        = you.skill_points;
2194     training_targets    = you.training_targets;
2195     ct_skill_points     = you.ct_skill_points;
2196     skill_cost_level    = you.skill_cost_level;
2197     skill_order         = you.skill_order;
2198     auto_training       = you.auto_training;
2199     exp_available       = you.exp_available;
2200     total_experience    = you.total_experience;
2201     skill_manual_points = you.skill_manual_points;
2202     for (int i = 0; i < NUM_SKILLS; i++)
2203     {
2204         real_skills[i] = you.skill((skill_type)i, 10, true);
2205         changed_skills[i] = you.skill((skill_type)i, 10);
2206     }
2207     saved = true;
2208 }
2209 
state_saved() const2210 bool skill_state::state_saved() const
2211 {
2212     return saved;
2213 }
2214 
restore_levels()2215 void skill_state::restore_levels()
2216 {
2217     you.skills                      = skills;
2218     you.skill_points                = skill_points;
2219     you.ct_skill_points             = ct_skill_points;
2220     you.skill_cost_level            = skill_cost_level;
2221     you.skill_order                 = skill_order;
2222     you.exp_available               = exp_available;
2223     you.total_experience            = total_experience;
2224     you.skill_manual_points         = skill_manual_points;
2225 }
2226 
restore_training()2227 void skill_state::restore_training()
2228 {
2229     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
2230     {
2231         // Don't resume training if it's impossible or a target was met
2232         // after our backup was made.
2233         if (you.skills[sk] < MAX_SKILL_LEVEL)
2234         {
2235             you.train[sk] = train[sk];
2236             you.training_targets[sk] = training_targets[sk];
2237         }
2238     }
2239 
2240     you.can_currently_train         = can_currently_train;
2241     you.auto_training               = auto_training;
2242     reset_training();
2243     check_training_targets();
2244 }
2245 
2246 // Sanitize skills after an upgrade, racechange, etc.
fixup_skills()2247 void fixup_skills()
2248 {
2249     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
2250     {
2251         if (is_useless_skill(sk))
2252         {
2253             you.skill_points[sk] = 0;
2254             // gnolls have everything existent enabled, so that the
2255             // training percentage is calculated correctly. (Useless
2256             // skills still won't be trained for them.)
2257             if (you.has_mutation(MUT_DISTRIBUTED_TRAINING)
2258                 && !is_removed_skill(sk))
2259             {
2260                 you.train[sk] = TRAINING_ENABLED;
2261             }
2262             else
2263                 you.train[sk] = TRAINING_DISABLED;
2264         }
2265         else if (you.has_mutation(MUT_DISTRIBUTED_TRAINING))
2266             you.train[sk] = TRAINING_ENABLED;
2267         you.skill_points[sk] = min(you.skill_points[sk],
2268                                    skill_exp_needed(MAX_SKILL_LEVEL, sk));
2269         check_skill_level_change(sk);
2270     }
2271     init_can_currently_train();
2272     reset_training();
2273 
2274     if (you.exp_available >= 10 * calc_skill_cost(you.skill_cost_level)
2275         && !you.has_mutation(MUT_DISTRIBUTED_TRAINING))
2276     {
2277         skill_menu(SKMF_EXPERIENCE);
2278     }
2279 
2280     check_training_targets();
2281 }
2282 
2283 /** Can the player enable training for this skill?
2284  *
2285  * @param sk The skill to check.
2286  * @param override if true, don't consider whether the skill is currently
2287  *                 untrainable / harmful.
2288  * @returns True if the skill can be enabled for training, false otherwise.
2289  */
can_enable_skill(skill_type sk,bool override)2290 bool can_enable_skill(skill_type sk, bool override)
2291 {
2292     // TODO: should this check you.skill_points or you.skills?
2293     return !you.has_mutation(MUT_DISTRIBUTED_TRAINING)
2294        && you.skills[sk] < MAX_SKILL_LEVEL
2295        && !is_useless_skill(sk)
2296        && (override || (you.can_currently_train[sk] && !is_harmful_skill(sk)));
2297 }
2298