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