OSDN Git Service

Replace sprintf() with std::string and/or format(). Does part of the work of resolvi...
[hengbandforosx/hengbandosx.git] / src / hpmp / hp-mp-processor.cpp
1 #include "hpmp/hp-mp-processor.h"
2 #include "avatar/avatar.h"
3 #include "cmd-action/cmd-pet.h"
4 #include "core/player-redraw-types.h"
5 #include "core/window-redrawer.h"
6 #include "flavor/flavor-describer.h"
7 #include "flavor/object-flavor-types.h"
8 #include "floor/pattern-walk.h"
9 #include "grid/grid.h"
10 #include "hpmp/hp-mp-regenerator.h"
11 #include "inventory/inventory-slot-types.h"
12 #include "main/sound-definitions-table.h"
13 #include "main/sound-of-music.h"
14 #include "monster-race/monster-race.h"
15 #include "monster-race/race-flags2.h"
16 #include "monster-race/race-flags3.h"
17 #include "object-enchant/object-ego.h"
18 #include "object-enchant/tr-types.h"
19 #include "object-enchant/trc-types.h"
20 #include "object/object-flags.h"
21 #include "object/tval-types.h"
22 #include "pet/pet-util.h"
23 #include "player-base/player-class.h"
24 #include "player-base/player-race.h"
25 #include "player-info/monk-data-type.h"
26 #include "player-info/race-info.h"
27 #include "player-info/race-types.h"
28 #include "player-info/samurai-data-type.h"
29 #include "player/attack-defense-types.h"
30 #include "player/digestion-processor.h"
31 #include "player/player-damage.h"
32 #include "player/player-status-flags.h"
33 #include "player/player-status-resist.h"
34 #include "player/player-status.h"
35 #include "player/special-defense-types.h"
36 #include "status/bad-status-setter.h"
37 #include "status/element-resistance.h"
38 #include "system/dungeon-info.h"
39 #include "system/floor-type-definition.h"
40 #include "system/grid-type-definition.h"
41 #include "system/item-entity.h"
42 #include "system/monster-entity.h"
43 #include "system/monster-race-info.h"
44 #include "system/player-type-definition.h"
45 #include "system/terrain-type-definition.h"
46 #include "timed-effect/player-cut.h"
47 #include "timed-effect/player-poison.h"
48 #include "timed-effect/timed-effects.h"
49 #include "util/bit-flags-calculator.h"
50 #include "view/display-messages.h"
51 #include "world/world.h"
52 #include <functional>
53
54 /*!
55  * @brief 地形によるダメージを与える / Deal damage from feature.
56  * @param player_ptr プレイヤー情報への参照ポインタ
57  * @param g_ptr 現在の床の情報への参照ポインタ
58  * @param msg_levitation 浮遊時にダメージを受けた場合に表示するメッセージ
59  * @param msg_normal 通常時にダメージを受けた場合に表示するメッセージの述部
60  * @param 耐性等によるダメージレートを計算する関数
61  * @param ダメージを受けた際の追加処理を行う関数
62  * @return ダメージを与えたらTRUE、なければFALSE
63  * @details
64  * ダメージを受けた場合、自然回復できない。
65  */
66 static bool deal_damege_by_feat(PlayerType *player_ptr, grid_type *g_ptr, concptr msg_levitation, concptr msg_normal,
67     std::function<PERCENTAGE(PlayerType *)> damage_rate, std::function<void(PlayerType *, int)> additional_effect)
68 {
69     auto *f_ptr = &terrains_info[g_ptr->feat];
70     int damage = 0;
71
72     if (f_ptr->flags.has(TerrainCharacteristics::DEEP)) {
73         damage = 6000 + randint0(4000);
74     } else if (!player_ptr->levitation) {
75         damage = 3000 + randint0(2000);
76     }
77
78     damage *= damage_rate(player_ptr);
79     damage /= 100;
80     if (player_ptr->levitation) {
81         damage /= 5;
82     }
83
84     damage = damage / 100 + (randint0(100) < (damage % 100));
85
86     if (damage == 0) {
87         return false;
88     }
89
90     if (player_ptr->levitation) {
91         msg_print(msg_levitation);
92
93         take_hit(player_ptr, DAMAGE_NOESCAPE, damage, format(_("%sの上に浮遊したダメージ", "flying over %s"), terrains_info[g_ptr->get_feat_mimic()].name.data()));
94
95         if (additional_effect != nullptr) {
96             additional_effect(player_ptr, damage);
97         }
98     } else {
99         concptr name = terrains_info[player_ptr->current_floor_ptr->grid_array[player_ptr->y][player_ptr->x].get_feat_mimic()].name.data();
100         msg_format(_("%s%s!", "The %s %s!"), name, msg_normal);
101         take_hit(player_ptr, DAMAGE_NOESCAPE, damage, name);
102
103         if (additional_effect != nullptr) {
104             additional_effect(player_ptr, damage);
105         }
106     }
107
108     return true;
109 }
110
111 /*!
112  * @brief 10ゲームターンが進行するごとにプレイヤーのHPとMPの増減処理を行う。
113  *  / Handle timed damage and regeneration every 10 game turns
114  */
115 void process_player_hp_mp(PlayerType *player_ptr)
116 {
117     auto &floor_ref = *player_ptr->current_floor_ptr;
118     auto *g_ptr = &floor_ref.grid_array[player_ptr->y][player_ptr->x];
119     auto *f_ptr = &terrains_info[g_ptr->feat];
120     bool cave_no_regen = false;
121     int upkeep_factor = 0;
122     int regen_amount = PY_REGEN_NORMAL;
123     const auto effects = player_ptr->effects();
124     const auto player_poison = effects->poison();
125     if (player_poison->is_poisoned() && !is_invuln(player_ptr)) {
126         if (take_hit(player_ptr, DAMAGE_NOESCAPE, 1, _("毒", "poison")) > 0) {
127             sound(SOUND_DAMAGE_OVER_TIME);
128         }
129     }
130
131     const auto player_cut = effects->cut();
132     if (player_cut->is_cut() && !is_invuln(player_ptr)) {
133         auto dam = player_cut->get_damage();
134         if (take_hit(player_ptr, DAMAGE_NOESCAPE, dam, _("致命傷", "a mortal wound")) > 0) {
135             sound(SOUND_DAMAGE_OVER_TIME);
136         }
137     }
138
139     const PlayerRace race(player_ptr);
140     if (race.life() == PlayerRaceLifeType::UNDEAD && race.tr_flags().has(TR_VUL_LITE)) {
141         if (!floor_ref.is_in_dungeon() && !has_resist_lite(player_ptr) && !is_invuln(player_ptr) && is_daytime()) {
142             if ((floor_ref.grid_array[player_ptr->y][player_ptr->x].info & (CAVE_GLOW | CAVE_MNDK)) == CAVE_GLOW) {
143                 msg_print(_("日光があなたのアンデッドの肉体を焼き焦がした!", "The sun's rays scorch your undead flesh!"));
144                 take_hit(player_ptr, DAMAGE_NOESCAPE, 1, _("日光", "sunlight"));
145                 cave_no_regen = true;
146             }
147         }
148
149         ItemEntity *o_ptr;
150         o_ptr = &player_ptr->inventory_list[INVEN_LITE];
151         auto flgs = object_flags(o_ptr);
152
153         if ((player_ptr->inventory_list[INVEN_LITE].bi_key.tval() != ItemKindType::NONE) && flgs.has_not(TR_DARK_SOURCE) && !has_resist_lite(player_ptr)) {
154             GAME_TEXT o_name[MAX_NLEN];
155             describe_flavor(player_ptr, o_name, o_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
156             msg_format(_("%sがあなたのアンデッドの肉体を焼き焦がした!", "The %s scorches your undead flesh!"), o_name);
157
158             cave_no_regen = true;
159
160             if (!is_invuln(player_ptr)) {
161                 describe_flavor(player_ptr, o_name, o_ptr, OD_NAME_ONLY);
162                 take_hit(player_ptr, DAMAGE_NOESCAPE, 1, std::string(_(o_name, "wielding ")).append(_("を装備したダメージ", o_name)).data());
163             }
164         }
165     }
166
167     if (f_ptr->flags.has(TerrainCharacteristics::LAVA) && !is_invuln(player_ptr) && !has_immune_fire(player_ptr)) {
168         if (deal_damege_by_feat(
169                 player_ptr, g_ptr, _("熱で火傷した!", "The heat burns you!"), _("で火傷した!", "burns you!"), calc_fire_damage_rate, nullptr)) {
170             cave_no_regen = true;
171             sound(SOUND_TERRAIN_DAMAGE);
172         }
173     }
174
175     if (f_ptr->flags.has(TerrainCharacteristics::COLD_PUDDLE) && !is_invuln(player_ptr) && !has_immune_cold(player_ptr)) {
176         if (deal_damege_by_feat(
177                 player_ptr, g_ptr, _("冷気に覆われた!", "The cold engulfs you!"), _("に凍えた!", "frostbites you!"), calc_cold_damage_rate, nullptr)) {
178             cave_no_regen = true;
179             sound(SOUND_TERRAIN_DAMAGE);
180         }
181     }
182
183     if (f_ptr->flags.has(TerrainCharacteristics::ELEC_PUDDLE) && !is_invuln(player_ptr) && !has_immune_elec(player_ptr)) {
184         if (deal_damege_by_feat(
185                 player_ptr, g_ptr, _("電撃を受けた!", "The electricity shocks you!"), _("に感電した!", "shocks you!"), calc_elec_damage_rate, nullptr)) {
186             cave_no_regen = true;
187             sound(SOUND_TERRAIN_DAMAGE);
188         }
189     }
190
191     if (f_ptr->flags.has(TerrainCharacteristics::ACID_PUDDLE) && !is_invuln(player_ptr) && !has_immune_acid(player_ptr)) {
192         if (deal_damege_by_feat(
193                 player_ptr, g_ptr, _("酸が飛び散った!", "The acid melts you!"), _("に溶かされた!", "melts you!"), calc_acid_damage_rate, nullptr)) {
194             cave_no_regen = true;
195             sound(SOUND_TERRAIN_DAMAGE);
196         }
197     }
198
199     if (f_ptr->flags.has(TerrainCharacteristics::POISON_PUDDLE) && !is_invuln(player_ptr)) {
200         if (deal_damege_by_feat(player_ptr, g_ptr, _("毒気を吸い込んだ!", "The gas poisons you!"), _("に毒された!", "poisons you!"), calc_acid_damage_rate,
201                 [](PlayerType *player_ptr, int damage) {
202                     if (!has_resist_pois(player_ptr)) {
203                         (void)BadStatusSetter(player_ptr).mod_poison(static_cast<TIME_EFFECT>(damage));
204                     }
205                 })) {
206             cave_no_regen = true;
207             sound(SOUND_TERRAIN_DAMAGE);
208         }
209     }
210
211     if (f_ptr->flags.has_all_of({ TerrainCharacteristics::WATER, TerrainCharacteristics::DEEP }) && !player_ptr->levitation && !player_ptr->can_swim && !has_resist_water(player_ptr)) {
212         if (calc_inventory_weight(player_ptr) > calc_weight_limit(player_ptr)) {
213             msg_print(_("溺れている!", "You are drowning!"));
214             take_hit(player_ptr, DAMAGE_NOESCAPE, randint1(player_ptr->lev), _("溺れ", "drowning"));
215             cave_no_regen = true;
216             sound(SOUND_TERRAIN_DAMAGE);
217         }
218     }
219
220     if (get_player_flags(player_ptr, TR_SELF_FIRE) && !has_immune_fire(player_ptr)) {
221         int damage;
222         damage = player_ptr->lev;
223         if (race.tr_flags().has(TR_VUL_FIRE)) {
224             damage += damage / 3;
225         }
226         if (has_resist_fire(player_ptr)) {
227             damage = damage / 3;
228         }
229         if (is_oppose_fire(player_ptr)) {
230             damage = damage / 3;
231         }
232
233         damage = std::max(damage, 1);
234         msg_print(_("熱い!", "It's hot!"));
235         take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("炎のオーラ", "Fire aura"));
236     }
237
238     if (get_player_flags(player_ptr, TR_SELF_ELEC) && !has_immune_elec(player_ptr)) {
239         int damage;
240         damage = player_ptr->lev;
241         if (race.tr_flags().has(TR_VUL_ELEC)) {
242             damage += damage / 3;
243         }
244         if (has_resist_elec(player_ptr)) {
245             damage = damage / 3;
246         }
247         if (is_oppose_elec(player_ptr)) {
248             damage = damage / 3;
249         }
250
251         damage = std::max(damage, 1);
252         msg_print(_("痛い!", "It hurts!"));
253         take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("電気のオーラ", "Elec aura"));
254     }
255
256     if (get_player_flags(player_ptr, TR_SELF_COLD) && !has_immune_cold(player_ptr)) {
257         int damage;
258         damage = player_ptr->lev;
259         if (race.tr_flags().has(TR_VUL_COLD)) {
260             damage += damage / 3;
261         }
262         if (has_resist_cold(player_ptr)) {
263             damage = damage / 3;
264         }
265         if (is_oppose_cold(player_ptr)) {
266             damage = damage / 3;
267         }
268
269         damage = std::max(damage, 1);
270         msg_print(_("冷たい!", "It's cold!"));
271         take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("冷気のオーラ", "Cold aura"));
272     }
273
274     if (player_ptr->riding) {
275         int damage;
276         auto auras = monraces_info[floor_ref.m_list[player_ptr->riding].r_idx].aura_flags;
277         if (auras.has(MonsterAuraType::FIRE) && !has_immune_fire(player_ptr)) {
278             damage = monraces_info[floor_ref.m_list[player_ptr->riding].r_idx].level / 2;
279             if (race.tr_flags().has(TR_VUL_FIRE)) {
280                 damage += damage / 3;
281             }
282             if (has_resist_fire(player_ptr)) {
283                 damage = damage / 3;
284             }
285             if (is_oppose_fire(player_ptr)) {
286                 damage = damage / 3;
287             }
288
289             damage = std::max(damage, 1);
290             msg_print(_("熱い!", "It's hot!"));
291             take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("炎のオーラ", "Fire aura"));
292         }
293
294         if (auras.has(MonsterAuraType::ELEC) && !has_immune_elec(player_ptr)) {
295             damage = monraces_info[floor_ref.m_list[player_ptr->riding].r_idx].level / 2;
296             if (race.tr_flags().has(TR_VUL_ELEC)) {
297                 damage += damage / 3;
298             }
299             if (has_resist_elec(player_ptr)) {
300                 damage = damage / 3;
301             }
302             if (is_oppose_elec(player_ptr)) {
303                 damage = damage / 3;
304             }
305
306             damage = std::max(damage, 1);
307             msg_print(_("痛い!", "It hurts!"));
308             take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("電気のオーラ", "Elec aura"));
309         }
310
311         if (auras.has(MonsterAuraType::COLD) && !has_immune_cold(player_ptr)) {
312             damage = monraces_info[floor_ref.m_list[player_ptr->riding].r_idx].level / 2;
313             if (race.tr_flags().has(TR_VUL_COLD)) {
314                 damage += damage / 3;
315             }
316             if (has_resist_cold(player_ptr)) {
317                 damage = damage / 3;
318             }
319             if (is_oppose_cold(player_ptr)) {
320                 damage = damage / 3;
321             }
322
323             damage = std::max(damage, 1);
324             msg_print(_("冷たい!", "It's cold!"));
325             take_hit(player_ptr, DAMAGE_NOESCAPE, damage, _("冷気のオーラ", "Cold aura"));
326         }
327     }
328
329     /* Spectres -- take damage when moving through walls */
330     /*
331      * Added: ANYBODY takes damage if inside through walls
332      * without wraith form -- NOTE: Spectres will never be
333      * reduced below 0 hp by being inside a stone wall; others
334      * WILL BE!
335      */
336     if (f_ptr->flags.has_none_of({ TerrainCharacteristics::MOVE, TerrainCharacteristics::CAN_FLY })) {
337         if (!is_invuln(player_ptr) && !player_ptr->wraith_form && !player_ptr->tim_pass_wall && ((player_ptr->chp > (player_ptr->lev / 5)) || !has_pass_wall(player_ptr))) {
338             concptr dam_desc;
339             cave_no_regen = true;
340
341             if (has_pass_wall(player_ptr)) {
342                 msg_print(_("体の分子が分解した気がする!", "Your molecules feel disrupted!"));
343                 dam_desc = _("密度", "density");
344             } else {
345                 msg_print(_("崩れた岩に押し潰された!", "You are being crushed!"));
346                 dam_desc = _("硬い岩", "solid rock");
347             }
348
349             take_hit(player_ptr, DAMAGE_NOESCAPE, 1 + (player_ptr->lev / 5), dam_desc);
350         }
351     }
352
353     if (player_ptr->food < PY_FOOD_WEAK) {
354         if (player_ptr->food < PY_FOOD_STARVE) {
355             regen_amount = 0;
356         } else if (player_ptr->food < PY_FOOD_FAINT) {
357             regen_amount = PY_REGEN_FAINT;
358         } else {
359             regen_amount = PY_REGEN_WEAK;
360         }
361     }
362
363     PlayerClass pc(player_ptr);
364     if (pattern_effect(player_ptr)) {
365         cave_no_regen = true;
366     } else {
367         if (player_ptr->regenerate) {
368             regen_amount = regen_amount * 2;
369         }
370
371         if (!pc.monk_stance_is(MonkStanceType::NONE) || !pc.samurai_stance_is(SamuraiStanceType::NONE)) {
372             regen_amount /= 2;
373         }
374         if (player_ptr->cursed.has(CurseTraitType::SLOW_REGEN)) {
375             regen_amount /= 5;
376         }
377     }
378
379     if ((player_ptr->action == ACTION_SEARCH) || (player_ptr->action == ACTION_REST)) {
380         regen_amount = regen_amount * 2;
381     }
382
383     upkeep_factor = calculate_upkeep(player_ptr);
384     if ((player_ptr->action == ACTION_LEARN) || (player_ptr->action == ACTION_HAYAGAKE) || pc.samurai_stance_is(SamuraiStanceType::KOUKIJIN)) {
385         upkeep_factor += 100;
386     }
387
388     regenmana(player_ptr, upkeep_factor, regen_amount);
389     if (pc.equals(PlayerClassType::MAGIC_EATER)) {
390         regenmagic(player_ptr, regen_amount);
391     }
392
393     if ((player_ptr->csp == 0) && (player_ptr->csp_frac == 0)) {
394         while (upkeep_factor > 100) {
395             msg_print(_("こんなに多くのペットを制御できない!", "Too many pets to control at once!"));
396             msg_print(nullptr);
397             do_cmd_pet_dismiss(player_ptr);
398
399             upkeep_factor = calculate_upkeep(player_ptr);
400
401             msg_format(_("維持MPは %d%%", "Upkeep: %d%% mana."), upkeep_factor);
402             msg_print(nullptr);
403         }
404     }
405
406     if (player_poison->is_poisoned()) {
407         regen_amount = 0;
408     }
409     if (player_cut->is_cut()) {
410         regen_amount = 0;
411     }
412     if (cave_no_regen) {
413         regen_amount = 0;
414     }
415
416     regen_amount = (regen_amount * player_ptr->mutant_regenerate_mod) / 100;
417     if ((player_ptr->chp < player_ptr->mhp) && !cave_no_regen) {
418         regenhp(player_ptr, regen_amount);
419     }
420 }
421
422 /*
423  * Increase players hit points, notice effects
424  */
425 bool hp_player(PlayerType *player_ptr, int num)
426 {
427     int vir;
428     vir = virtue_number(player_ptr, V_VITALITY);
429
430     if (num <= 0) {
431         return false;
432     }
433
434     if (vir) {
435         num = num * (player_ptr->virtues[vir - 1] + 1250) / 1250;
436     }
437
438     if (player_ptr->chp < player_ptr->mhp) {
439         if ((num > 0) && (player_ptr->chp < (player_ptr->mhp / 3))) {
440             chg_virtue(player_ptr, V_TEMPERANCE, 1);
441         }
442
443         player_ptr->chp += num;
444         if (player_ptr->chp >= player_ptr->mhp) {
445             player_ptr->chp = player_ptr->mhp;
446             player_ptr->chp_frac = 0;
447         }
448
449         player_ptr->redraw |= (PR_HP);
450         player_ptr->window_flags |= (PW_PLAYER);
451         if (num < 5) {
452             msg_print(_("少し気分が良くなった。", "You feel a little better."));
453         } else if (num < 15) {
454             msg_print(_("気分が良くなった。", "You feel better."));
455         } else if (num < 35) {
456             msg_print(_("とても気分が良くなった。", "You feel much better."));
457         } else {
458             msg_print(_("ひじょうに気分が良くなった。", "You feel very good."));
459         }
460
461         return true;
462     }
463
464     return false;
465 }