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