1 #include "AppHdr.h"
2 
3 #include "player-stats.h"
4 
5 #include "artefact.h"
6 #include "clua.h"
7 #include "delay.h"
8 #include "duration-type.h"
9 #include "equipment-type.h"
10 #include "files.h"
11 #include "god-passive.h"
12 #include "hints.h"
13 #include "libutil.h"
14 #include "macro.h"
15 #ifdef TOUCH_UI
16 #include "menu.h"
17 #endif
18 #include "message.h"
19 #include "monster.h"
20 #include "notes.h"
21 #include "ouch.h"
22 #include "options.h"
23 #include "output.h"
24 #include "player.h"
25 #include "religion.h"
26 #include "stat-type.h"
27 #include "state.h"
28 #include "stringutil.h"
29 #include "tag-version.h"
30 #ifdef TOUCH_UI
31 #include "rltiles/tiledef-gui.h"
32 #include "tilepick.h"
33 #endif
34 #include "transform.h"
35 
stat(stat_type s,bool nonneg) const36 int player::stat(stat_type s, bool nonneg) const
37 {
38     const int val = max_stat(s) - stat_loss[s];
39     return nonneg ? max(val, 0) : val;
40 }
41 
strength(bool nonneg) const42 int player::strength(bool nonneg) const
43 {
44     return stat(STAT_STR, nonneg);
45 }
46 
intel(bool nonneg) const47 int player::intel(bool nonneg) const
48 {
49     return stat(STAT_INT, nonneg);
50 }
51 
dex(bool nonneg) const52 int player::dex(bool nonneg) const
53 {
54     return stat(STAT_DEX, nonneg);
55 }
56 
57 static int _stat_modifier(stat_type stat, bool innate_only);
58 
59 /**
60  * What's the player's current maximum for a stat, before ability damage is
61  * applied?
62  *
63  * @param s      The stat in question (e.g. STAT_STR).
64  * @param innate Whether to disregard stat modifiers other than those from
65  *               innate mutations.
66  * @return      The player's maximum for the given stat; capped at
67  *              MAX_STAT_VALUE.
68  */
max_stat(stat_type s,bool innate) const69 int player::max_stat(stat_type s, bool innate) const
70 {
71     return min(base_stats[s] + _stat_modifier(s, innate), MAX_STAT_VALUE);
72 }
73 
max_strength() const74 int player::max_strength() const
75 {
76     return max_stat(STAT_STR);
77 }
78 
max_intel() const79 int player::max_intel() const
80 {
81     return max_stat(STAT_INT);
82 }
83 
max_dex() const84 int player::max_dex() const
85 {
86     return max_stat(STAT_DEX);
87 }
88 
89 // Base stat including innate mutations (which base_stats does not)
innate_stat(stat_type s)90 int innate_stat(stat_type s)
91 {
92     return you.max_stat(s, true);
93 }
94 
95 
96 static void _handle_stat_change(stat_type stat);
97 
98 /**
99  * Handle manual, permanent character stat increases. (Usually from every third
100  * XL.
101  *
102  * @return Whether the stat was actually increased (HUPs can interrupt this).
103  */
attribute_increase()104 bool attribute_increase()
105 {
106     const bool need_caps = Options.easy_confirm != easy_confirm_type::all;
107 
108     const int statgain = species::get_stat_gain_multiplier(you.species);
109 
110     const string stat_gain_message = make_stringf("Your experience leads to a%s "
111                                                   "increase in your attributes!",
112                                                   (statgain > 1) ?
113                                                   " dramatic" : "n");
114     crawl_state.stat_gain_prompt = true;
115 #ifdef TOUCH_UI
116     learned_something_new(HINT_CHOOSE_STAT);
117     Menu pop(MF_SINGLESELECT | MF_ANYPRINTABLE);
118     MenuEntry * const status = new MenuEntry("", MEL_SUBTITLE);
119     MenuEntry * const s_me = new MenuEntry("Strength", MEL_ITEM, 1,
120                                                         need_caps ? 'S' : 's');
121     s_me->add_tile(tile_def(TILEG_FIGHTING_ON));
122     MenuEntry * const i_me = new MenuEntry("Intelligence", MEL_ITEM, 1,
123                                                         need_caps ? 'I' : 'i');
124     i_me->add_tile(tile_def(TILEG_SPELLCASTING_ON));
125     MenuEntry * const d_me = new MenuEntry("Dexterity", MEL_ITEM, 1,
126                                                         need_caps ? 'D' : 'd');
127     d_me->add_tile(tile_def(TILEG_DODGING_ON));
128 
129     pop.set_title(new MenuEntry("Increase Attributes", MEL_TITLE));
130     pop.add_entry(new MenuEntry(stat_gain_message + " Increase:", MEL_TITLE));
131     pop.add_entry(status);
132     pop.add_entry(s_me);
133     pop.add_entry(i_me);
134     pop.add_entry(d_me);
135 #else
136     mprf(MSGCH_INTRINSIC_GAIN, "%s", stat_gain_message.c_str());
137     learned_something_new(HINT_CHOOSE_STAT);
138     if (innate_stat(STAT_STR) != you.strength()
139         || innate_stat(STAT_INT) != you.intel()
140         || innate_stat(STAT_DEX) != you.dex())
141     {
142         mprf(MSGCH_PROMPT, "Your base attributes are Str %d, Int %d, Dex %d.",
143              innate_stat(STAT_STR),
144              innate_stat(STAT_INT),
145              innate_stat(STAT_DEX));
146     }
147     mprf(MSGCH_PROMPT, need_caps
148         ? "Increase (S)trength, (I)ntelligence, or (D)exterity? "
149         : "Increase (s)trength, (i)ntelligence, or (d)exterity? ");
150 #endif
151     mouse_control mc(MOUSE_MODE_PROMPT);
152 
153     bool tried_lua = false;
154     int keyin;
155     while (true)
156     {
157         // Calling a user-defined lua function here to let players reply to
158         // the prompt automatically. Either returning a string or using
159         // crawl.sendkeys will work.
160         if (!tried_lua && clua.callfn("choose_stat_gain", 0, 1))
161         {
162             string result;
163             clua.fnreturns(">s", &result);
164             keyin = toupper_safe(result[0]);
165         }
166         else
167         {
168 #ifdef TOUCH_UI
169             pop.show();
170             keyin = pop.getkey();
171 #else
172             while ((keyin = getchm()) == CK_REDRAW)
173             {
174                 redraw_screen();
175                 update_screen();
176             }
177 #endif
178         }
179         tried_lua = true;
180 
181         if (!need_caps)
182             keyin = toupper_safe(keyin);
183 
184         switch (keyin)
185         {
186         CASE_ESCAPE
187             // It is unsafe to save the game here; continue with the turn
188             // normally, when the player reloads, the game will re-prompt
189             // for their level-up stat gain.
190             if (crawl_state.seen_hups)
191                 return false;
192             break;
193 
194         case 'S':
195             for (int i = 0; i < statgain; i++)
196                 modify_stat(STAT_STR, 1, false);
197             return true;
198 
199         case 'I':
200             for (int i = 0; i < statgain; i++)
201                 modify_stat(STAT_INT, 1, false);
202             return true;
203 
204         case 'D':
205             for (int i = 0; i < statgain; i++)
206                 modify_stat(STAT_DEX, 1, false);
207             return true;
208 
209         case 's':
210         case 'i':
211         case 'd':
212 #ifdef TOUCH_UI
213             status->text = "Uppercase letters only, please.";
214 #else
215             mprf(MSGCH_PROMPT, "Uppercase letters only, please.");
216 #endif
217             break;
218 #ifdef TOUCH_UI
219         default:
220             status->text = "Please choose an option below"; // too naggy?
221 #endif
222         }
223     }
224 }
225 
226 /*
227  * Have Jiyva increase a player stat by one and decrease a different stat by
228  * one.
229  *
230  * This considers armour evp and skills to determine which stats to change. A
231  * target stat vector is created based on these factors, which is then fuzzed,
232  * and then a shuffle of the player's stat points that doesn't increase the l^2
233  * distance to the target vector is chosen.
234 */
jiyva_stat_action()235 void jiyva_stat_action()
236 {
237     int cur_stat[NUM_STATS];
238     int stat_total = 0;
239     int target_stat[NUM_STATS];
240     for (int x = 0; x < NUM_STATS; ++x)
241     {
242         cur_stat[x] = you.stat(static_cast<stat_type>(x), false);
243         stat_total += cur_stat[x];
244     }
245 
246     int evp = you.unadjusted_body_armour_penalty();
247     target_stat[STAT_STR] = max(9, evp);
248     target_stat[STAT_INT] = 9;
249     target_stat[STAT_DEX] = 9;
250     int remaining = stat_total - 18 - target_stat[0];
251 
252     // Divide up the remaining stat points between Int and either Str or Dex,
253     // based on skills.
254     if (remaining > 0)
255     {
256         int magic_weights = 0;
257         int other_weights = 0;
258         for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
259         {
260             int weight = you.skills[sk];
261 
262             if (sk >= SK_SPELLCASTING && sk < SK_INVOCATIONS)
263                 magic_weights += weight;
264             else
265                 other_weights += weight;
266         }
267         // We give pure Int weighting if the player is sufficiently
268         // focused on magic skills.
269         other_weights = max(other_weights - magic_weights / 2, 0);
270 
271         // Now scale appropriately and apply the Int weighting
272         magic_weights = div_rand_round(remaining * magic_weights,
273                                        magic_weights + other_weights);
274         other_weights = remaining - magic_weights;
275         target_stat[STAT_INT] += magic_weights;
276 
277         // Heavy armour weights towards Str, Dodging skill towards Dex.
278         int str_weight = 10 * evp;
279         int dex_weight = 10 + you.skill(SK_DODGING, 10);
280 
281         // Now apply the Str and Dex weighting.
282         const int str_adj = div_rand_round(other_weights * str_weight,
283                                            str_weight + dex_weight);
284         target_stat[STAT_STR] += str_adj;
285         target_stat[STAT_DEX] += (other_weights - str_adj);
286     }
287     // Add a little fuzz to the target.
288     for (int x = 0; x < NUM_STATS; ++x)
289         target_stat[x] += random2(5) - 2;
290     int choices = 0;
291     int stat_up_choice = 0;
292     int stat_down_choice = 0;
293     // Choose a random stat shuffle that doesn't increase the l^2 distance to
294     // the (fuzzed) target.
295     for (int gain = 0; gain < NUM_STATS; ++gain)
296         for (int lose = 0; lose < NUM_STATS; ++lose)
297         {
298             if (gain != lose && cur_stat[lose] > 1
299                 && target_stat[gain] - cur_stat[gain] > target_stat[lose] - cur_stat[lose]
300                 && cur_stat[gain] < MAX_STAT_VALUE && you.base_stats[lose] > 1)
301             {
302                 choices++;
303                 if (one_chance_in(choices))
304                 {
305                     stat_up_choice = gain;
306                     stat_down_choice = lose;
307                 }
308             }
309         }
310     if (choices)
311     {
312         simple_god_message("'s power touches on your attributes.");
313         modify_stat(static_cast<stat_type>(stat_up_choice), 1, false);
314         modify_stat(static_cast<stat_type>(stat_down_choice), -1, false);
315     }
316 }
317 
318 static const char* descs[NUM_STATS][NUM_STAT_DESCS] =
319 {
320     { "strength", "weakened", "weaker", "stronger" },
321     { "intelligence", "dopey", "stupid", "clever" },
322     { "dexterity", "clumsy", "clumsy", "agile" }
323 };
324 
stat_desc(stat_type stat,stat_desc_type desc)325 const char* stat_desc(stat_type stat, stat_desc_type desc)
326 {
327     return descs[stat][desc];
328 }
329 
modify_stat(stat_type which_stat,int amount,bool suppress_msg)330 void modify_stat(stat_type which_stat, int amount, bool suppress_msg)
331 {
332     ASSERT(!crawl_state.game_is_arena());
333 
334     // sanity - is non-zero amount?
335     if (amount == 0)
336         return;
337 
338     // Stop delays if a stat drops.
339     if (amount < 0)
340         interrupt_activity(activity_interrupt::stat_change);
341 
342     if (which_stat == STAT_RANDOM)
343         which_stat = static_cast<stat_type>(random2(NUM_STATS));
344 
345     if (!suppress_msg)
346     {
347         mprf((amount > 0) ? MSGCH_INTRINSIC_GAIN : MSGCH_WARN,
348              "You feel %s.",
349              stat_desc(which_stat, (amount > 0) ? SD_INCREASE : SD_DECREASE));
350     }
351 
352     you.base_stats[which_stat] += amount;
353 
354     _handle_stat_change(which_stat);
355 }
356 
notify_stat_change(stat_type which_stat,int amount,bool suppress_msg)357 void notify_stat_change(stat_type which_stat, int amount, bool suppress_msg)
358 {
359     ASSERT(!crawl_state.game_is_arena());
360 
361     // sanity - is non-zero amount?
362     if (amount == 0)
363         return;
364 
365     // Stop delays if a stat drops.
366     if (amount < 0)
367         interrupt_activity(activity_interrupt::stat_change);
368 
369     if (which_stat == STAT_RANDOM)
370         which_stat = static_cast<stat_type>(random2(NUM_STATS));
371 
372     if (!suppress_msg)
373     {
374         mprf((amount > 0) ? MSGCH_INTRINSIC_GAIN : MSGCH_WARN,
375              "You feel %s.",
376              stat_desc(which_stat, (amount > 0) ? SD_INCREASE : SD_DECREASE));
377     }
378 
379     _handle_stat_change(which_stat);
380 }
381 
notify_stat_change()382 void notify_stat_change()
383 {
384     for (int i = 0; i < NUM_STATS; ++i)
385         _handle_stat_change(static_cast<stat_type>(i));
386 }
387 
_mut_level(mutation_type mut,bool innate_only)388 static int _mut_level(mutation_type mut, bool innate_only)
389 {
390     return you.get_base_mutation_level(mut, true, !innate_only, !innate_only);
391 }
392 
_strength_modifier(bool innate_only)393 static int _strength_modifier(bool innate_only)
394 {
395     int result = 0;
396 
397     if (!innate_only)
398     {
399         if (you.duration[DUR_DIVINE_STAMINA])
400             result += you.attribute[ATTR_DIVINE_STAMINA];
401 
402         result += chei_stat_boost();
403 
404         // ego items of strength
405         result += 3 * count_worn_ego(SPARM_STRENGTH);
406 
407         // rings of strength
408         result += you.wearing(EQ_RINGS_PLUS, RING_STRENGTH);
409 
410         // randarts of strength
411         result += you.scan_artefacts(ARTP_STRENGTH);
412 
413         // form
414         result += get_form()->str_mod;
415     }
416 
417     // mutations
418     result += 2 * (_mut_level(MUT_STRONG, innate_only)
419                    - _mut_level(MUT_WEAK, innate_only));
420 #if TAG_MAJOR_VERSION == 34
421     result += _mut_level(MUT_STRONG_STIFF, innate_only)
422               - _mut_level(MUT_FLEXIBLE_WEAK, innate_only);
423 #endif
424 
425     return result;
426 }
427 
_int_modifier(bool innate_only)428 static int _int_modifier(bool innate_only)
429 {
430     int result = 0;
431 
432     if (!innate_only)
433     {
434         if (you.duration[DUR_DIVINE_STAMINA])
435             result += you.attribute[ATTR_DIVINE_STAMINA];
436 
437         result += chei_stat_boost();
438 
439         // ego items of intelligence
440         result += 3 * count_worn_ego(SPARM_INTELLIGENCE);
441 
442         // rings of intelligence
443         result += you.wearing(EQ_RINGS_PLUS, RING_INTELLIGENCE);
444 
445         // randarts of intelligence
446         result += you.scan_artefacts(ARTP_INTELLIGENCE);
447     }
448 
449     // mutations
450     result += 2 * (_mut_level(MUT_CLEVER, innate_only)
451                    - _mut_level(MUT_DOPEY, innate_only));
452     result += 2 * _mut_level(MUT_BIG_BRAIN, innate_only);
453 
454     return result;
455 }
456 
_dex_modifier(bool innate_only)457 static int _dex_modifier(bool innate_only)
458 {
459     int result = 0;
460 
461     if (!innate_only)
462     {
463         if (you.duration[DUR_DIVINE_STAMINA])
464             result += you.attribute[ATTR_DIVINE_STAMINA];
465 
466         result += chei_stat_boost();
467 
468         // ego items of dexterity
469         result += 3 * count_worn_ego(SPARM_DEXTERITY);
470 
471         // rings of dexterity
472         result += you.wearing(EQ_RINGS_PLUS, RING_DEXTERITY);
473 
474         // randarts of dexterity
475         result += you.scan_artefacts(ARTP_DEXTERITY);
476 
477         // form
478         result += get_form()->dex_mod;
479     }
480 
481     // mutations
482     result += 2 * (_mut_level(MUT_AGILE, innate_only)
483                   - _mut_level(MUT_CLUMSY, innate_only));
484 #if TAG_MAJOR_VERSION == 34
485     result += _mut_level(MUT_FLEXIBLE_WEAK, innate_only)
486               - _mut_level(MUT_STRONG_STIFF, innate_only);
487     result -= _mut_level(MUT_ROUGH_BLACK_SCALES, innate_only);
488 #endif
489     result += 2 * _mut_level(MUT_THIN_SKELETAL_STRUCTURE, innate_only);
490 
491     return result;
492 }
493 
_stat_modifier(stat_type stat,bool innate_only)494 static int _stat_modifier(stat_type stat, bool innate_only)
495 {
496     switch (stat)
497     {
498     case STAT_STR: return _strength_modifier(innate_only);
499     case STAT_INT: return _int_modifier(innate_only);
500     case STAT_DEX: return _dex_modifier(innate_only);
501     default:
502         mprf(MSGCH_ERROR, "Bad stat: %d", stat);
503         return 0;
504     }
505 }
506 
_stat_name(stat_type stat)507 static string _stat_name(stat_type stat)
508 {
509     switch (stat)
510     {
511     case STAT_STR:
512         return "strength";
513     case STAT_INT:
514         return "intelligence";
515     case STAT_DEX:
516         return "dexterity";
517     default:
518         die("invalid stat");
519     }
520 }
521 
stat_loss_roll()522 int stat_loss_roll()
523 {
524     const int loss = 30 + random2(30);
525     dprf("Stat loss points: %d", loss);
526 
527     return loss;
528 }
529 
lose_stat(stat_type which_stat,int stat_loss,bool force)530 bool lose_stat(stat_type which_stat, int stat_loss, bool force)
531 {
532     if (stat_loss <= 0)
533         return false;
534 
535     if (which_stat == STAT_RANDOM)
536         which_stat = static_cast<stat_type>(random2(NUM_STATS));
537 
538     if (!force)
539     {
540         if (you.duration[DUR_DIVINE_STAMINA] > 0)
541         {
542             mprf("Your divine stamina protects you from %s loss.",
543                  _stat_name(which_stat).c_str());
544             return false;
545         }
546     }
547 
548     mprf(MSGCH_WARN, "You feel %s.", stat_desc(which_stat, SD_LOSS));
549 
550     you.stat_loss[which_stat] = min<int>(100,
551                                          you.stat_loss[which_stat] + stat_loss);
552     if (!you.attribute[ATTR_STAT_LOSS_XP])
553         you.attribute[ATTR_STAT_LOSS_XP] = stat_loss_roll();
554     _handle_stat_change(which_stat);
555     return true;
556 }
557 
random_lost_stat()558 stat_type random_lost_stat()
559 {
560     stat_type choice = NUM_STATS;
561     int found = 0;
562     for (int i = 0; i < NUM_STATS; ++i)
563         if (you.stat_loss[i] > 0)
564         {
565             found++;
566             if (one_chance_in(found))
567                 choice = static_cast<stat_type>(i);
568         }
569     return choice;
570 }
571 
572 // Restore the stat in which_stat by the amount in stat_gain, displaying
573 // a message if suppress_msg is false, and doing so in the recovery
574 // channel if recovery is true. If stat_gain is 0, restore the stat
575 // completely.
restore_stat(stat_type which_stat,int stat_gain,bool suppress_msg,bool recovery)576 bool restore_stat(stat_type which_stat, int stat_gain,
577                   bool suppress_msg, bool recovery)
578 {
579     // A bit hackish, but cut me some slack, man! --
580     // Besides, a little recursion never hurt anyone {dlb}:
581     if (which_stat == STAT_ALL)
582     {
583         bool stat_restored = false;
584         for (int i = 0; i < NUM_STATS; ++i)
585             if (restore_stat((stat_type) i, stat_gain, suppress_msg))
586                 stat_restored = true;
587 
588         return stat_restored;
589     }
590 
591     if (which_stat == STAT_RANDOM)
592         which_stat = random_lost_stat();
593 
594     if (which_stat >= NUM_STATS || you.stat_loss[which_stat] == 0)
595         return false;
596 
597     if (!suppress_msg)
598     {
599         mprf(recovery ? MSGCH_RECOVERY : MSGCH_PLAIN,
600              "You feel your %s returning.",
601              _stat_name(which_stat).c_str());
602     }
603 
604     if (stat_gain == 0 || stat_gain > you.stat_loss[which_stat])
605         stat_gain = you.stat_loss[which_stat];
606 
607     you.stat_loss[which_stat] -= stat_gain;
608 
609     // If we're fully recovered, clear out stat loss recovery timer.
610     if (random_lost_stat() == NUM_STATS)
611         you.attribute[ATTR_STAT_LOSS_XP] = 0;
612 
613     _handle_stat_change(which_stat);
614     return true;
615 }
616 
_normalize_stat(stat_type stat)617 static void _normalize_stat(stat_type stat)
618 {
619     ASSERT(you.stat_loss[stat] >= 0);
620     you.base_stats[stat] = min<int8_t>(you.base_stats[stat], MAX_STAT_VALUE);
621 }
622 
_handle_stat_change(stat_type stat)623 static void _handle_stat_change(stat_type stat)
624 {
625     ASSERT_RANGE(stat, 0, NUM_STATS);
626 
627     if (you.stat(stat) <= 0 && !you.duration[stat_zero_duration(stat)])
628     {
629         // Time required for recovery once the stat is restored, randomised slightly.
630         you.duration[stat_zero_duration(stat)] =
631             (20 + random2(20)) * BASELINE_DELAY;
632         mprf(MSGCH_WARN, "You have lost your %s.", stat_desc(stat, SD_NAME));
633         take_note(Note(NOTE_MESSAGE, 0, 0, make_stringf("Lost %s.",
634             stat_desc(stat, SD_NAME)).c_str()), true);
635         // 2 to 5 turns of paralysis (XXX: decremented right away?)
636         you.increase_duration(DUR_PARALYSIS, 2 + random2(3));
637     }
638 
639     you.redraw_stats[stat] = true;
640     _normalize_stat(stat);
641 
642     switch (stat)
643     {
644     case STAT_STR:
645         you.redraw_armour_class = true; // includes shields
646         you.redraw_evasion = true; // Might reduce EV penalty
647         break;
648 
649     case STAT_INT:
650         break;
651 
652     case STAT_DEX:
653         you.redraw_evasion = true;
654         you.redraw_armour_class = true; // includes shields
655         break;
656 
657     default:
658         break;
659     }
660 }
661 
stat_zero_duration(stat_type stat)662 duration_type stat_zero_duration(stat_type stat)
663 {
664     switch (stat)
665     {
666     case STAT_STR:
667         return DUR_COLLAPSE;
668     case STAT_INT:
669         return DUR_BRAINLESS;
670     case STAT_DEX:
671         return DUR_CLUMSY;
672     default:
673         die("invalid stat");
674     }
675 }
676 
have_stat_zero()677 bool have_stat_zero()
678 {
679     for (int i = 0; i < NUM_STATS; ++i)
680         if (you.duration[stat_zero_duration(static_cast<stat_type> (i))])
681             return true;
682 
683     return false;
684 }
685