OSDN Git Service

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