OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / monster-attack / monster-attack-player.cpp
1 /*!
2  * @brief モンスターからプレイヤーへの直接攻撃処理
3  * @date 2020/05/23
4  * @author Hourier
5  */
6
7 #include "monster-attack/monster-attack-player.h"
8 #include "cmd-action/cmd-attack.h"
9 #include "cmd-action/cmd-pet.h"
10 #include "combat/attack-accuracy.h"
11 #include "combat/attack-criticality.h"
12 #include "combat/aura-counterattack.h"
13 #include "combat/combat-options-type.h"
14 #include "combat/hallucination-attacks-table.h"
15 #include "core/disturbance.h"
16 #include "dungeon/dungeon-flag-types.h"
17 #include "floor/geometry.h"
18 #include "inventory/inventory-slot-types.h"
19 #include "main/sound-definitions-table.h"
20 #include "main/sound-of-music.h"
21 #include "mind/mind-ninja.h"
22 #include "mind/mind-samurai.h"
23 #include "monster-attack/monster-attack-describer.h"
24 #include "monster-attack/monster-attack-effect.h"
25 #include "monster-attack/monster-attack-switcher.h"
26 #include "monster-attack/monster-attack-table.h"
27 #include "monster/monster-damage.h"
28 #include "monster/monster-describer.h"
29 #include "monster/monster-description-types.h"
30 #include "monster/monster-info.h"
31 #include "monster/monster-status.h"
32 #include "monster/smart-learn-types.h"
33 #include "object-hook/hook-armor.h"
34 #include "object/item-tester-hooker.h"
35 #include "pet/pet-fall-off.h"
36 #include "player-base/player-class.h"
37 #include "player-info/samurai-data-type.h"
38 #include "player/attack-defense-types.h"
39 #include "player/player-damage.h"
40 #include "player/player-skill.h"
41 #include "player/special-defense-types.h"
42 #include "spell-kind/spells-teleport.h"
43 #include "spell-realm/spells-hex.h"
44 #include "status/action-setter.h"
45 #include "status/bad-status-setter.h"
46 #include "system/angband.h"
47 #include "system/dungeon-info.h"
48 #include "system/floor-type-definition.h"
49 #include "system/item-entity.h"
50 #include "system/monster-entity.h"
51 #include "system/monster-race-info.h"
52 #include "system/player-type-definition.h"
53 #include "system/redrawing-flags-updater.h"
54 #include "timed-effect/timed-effects.h"
55 #include "util/bit-flags-calculator.h"
56 #include "util/string-processor.h"
57 #include "view/display-messages.h"
58
59 /*!
60  * @brief コンストラクタ
61  * @param player_ptr プレイヤーへの参照ポインタ
62  * @param m_idx 打撃を行うモンスターのID
63  */
64 MonsterAttackPlayer::MonsterAttackPlayer(PlayerType *player_ptr, short m_idx)
65     : m_idx(m_idx)
66     , m_ptr(&player_ptr->current_floor_ptr->m_list[m_idx])
67     , method(RaceBlowMethodType::NONE)
68     , effect(RaceBlowEffectType::NONE)
69     , do_silly_attack(one_in_(2) && player_ptr->effects()->hallucination().is_hallucinated())
70     , player_ptr(player_ptr)
71 {
72 }
73
74 /*!
75  * @brief モンスターからプレイヤーへの打撃処理 / Attack the player via physical attacks.
76  */
77 void MonsterAttackPlayer::make_attack_normal()
78 {
79     if (!this->check_no_blow()) {
80         return;
81     }
82
83     auto *r_ptr = &this->m_ptr->get_monrace();
84     this->rlev = ((r_ptr->level >= 1) ? r_ptr->level : 1);
85     angband_strcpy(this->m_name, monster_desc(this->player_ptr, this->m_ptr, 0), sizeof(this->m_name));
86     angband_strcpy(this->ddesc, monster_desc(this->player_ptr, this->m_ptr, MD_WRONGDOER_NAME), sizeof(this->ddesc));
87     if (PlayerClass(this->player_ptr).samurai_stance_is(SamuraiStanceType::IAI)) {
88         msg_print(_("相手が襲いかかる前に素早く武器を振るった。", format("You took sen, drew and cut in one motion before %s moved.", this->m_name)));
89         if (do_cmd_attack(this->player_ptr, this->m_ptr->fy, this->m_ptr->fx, HISSATSU_IAI)) {
90             return;
91         }
92     }
93
94     auto can_activate_kawarimi = randint0(55) < (this->player_ptr->lev * 3 / 5 + 20);
95     if (can_activate_kawarimi && kawarimi(this->player_ptr, true)) {
96         return;
97     }
98
99     this->blinked = false;
100     if (this->process_monster_blows()) {
101         return;
102     }
103
104     this->postprocess_monster_blows();
105 }
106
107 /*!
108  * @brief 能力値の実値を求める
109  * @param raw PlayerTypeに格納されている生値
110  * @return 実値
111  * @details AD&Dの記法に則り、19以上の値を取らなくしているので、格納方法が面倒
112  */
113 int MonsterAttackPlayer::stat_value(const int raw)
114 {
115     if (raw <= 18) {
116         return raw;
117     }
118
119     return (raw - 18) / 10 + 18;
120 }
121
122 bool MonsterAttackPlayer::check_no_blow()
123 {
124     auto *r_ptr = &this->m_ptr->get_monrace();
125     if (r_ptr->behavior_flags.has(MonsterBehaviorType::NEVER_BLOW)) {
126         return false;
127     }
128
129     if (this->player_ptr->current_floor_ptr->get_dungeon_definition().flags.has(DungeonFeatureType::NO_MELEE)) {
130         return false;
131     }
132
133     return this->m_ptr->is_hostile();
134 }
135
136 /*!
137  * @brief モンスターからプレイヤーへの打撃処理本体
138  * @return 打撃に反応してプレイヤーがその場から離脱したかどうか
139  */
140 bool MonsterAttackPlayer::process_monster_blows()
141 {
142     auto *r_ptr = &this->m_ptr->get_monrace();
143     for (auto ap_cnt = 0; ap_cnt < MAX_NUM_BLOWS; ap_cnt++) {
144         this->obvious = false;
145         this->damage = 0;
146         this->act = nullptr;
147         this->effect = r_ptr->blows[ap_cnt].effect;
148         this->method = r_ptr->blows[ap_cnt].method;
149         this->damage_dice = r_ptr->blows[ap_cnt].damage_dice;
150
151         if (!this->check_monster_continuous_attack()) {
152             break;
153         }
154
155         // effect が RaceBlowEffectType::NONE (無効値)になることはあり得ないはずだが、万一そう
156         // なっていたら単に攻撃を打ち切る。
157         // MonsterRaceDefinitions の "B:" トークンに effect 以降を書き忘れた場合が該当する。
158         if (this->effect == RaceBlowEffectType::NONE) {
159             plog("unexpected: MonsterAttackPlayer::effect == RaceBlowEffectType::NONE");
160             break;
161         }
162
163         // フレーバーの打撃は必中扱い。それ以外は通常の命中判定を行う。
164         this->ac = this->player_ptr->ac + this->player_ptr->to_a;
165         bool hit;
166         if (this->effect == RaceBlowEffectType::FLAVOR) {
167             hit = true;
168         } else {
169             const int power = mbe_info[enum2i(this->effect)].power;
170             hit = check_hit_from_monster_to_player(this->player_ptr, power, this->rlev, this->m_ptr->get_remaining_stun());
171         }
172
173         if (hit) {
174             // 命中した。命中処理と思い出処理を行う。
175             // 打撃そのものは対邪悪結界で撃退した可能性がある。
176             const bool protect = !this->process_monster_attack_hit();
177             this->increase_blow_type_seen(ap_cnt);
178
179             // 撃退成功時はそのまま次の打撃へ移行。
180             if (protect) {
181                 continue;
182             }
183
184             // 撃退失敗時は落馬処理、変わり身のテレポート処理を行う。
185             check_fall_off_horse(this->player_ptr, this);
186
187             // 変わり身のテレポートが成功したら攻撃を打ち切り、プレイヤーが離脱した旨を返す。
188             if (kawarimi(this->player_ptr, false)) {
189                 return true;
190             }
191         } else {
192             // 命中しなかった。回避時の処理、思い出処理を行う。
193             this->process_monster_attack_evasion();
194             this->increase_blow_type_seen(ap_cnt);
195         }
196     }
197
198     // 通常はプレイヤーはその場にとどまる。
199     return false;
200 }
201
202 /*!
203  * @brief プレイヤー死亡等でモンスターからプレイヤーへの直接攻撃処理を途中で打ち切るかどうかを判定する
204  * @return 攻撃続行ならばTRUE、打ち切りになったらFALSE
205  */
206 bool MonsterAttackPlayer::check_monster_continuous_attack()
207 {
208     if (!this->m_ptr->is_valid() || (this->method == RaceBlowMethodType::NONE)) {
209         return false;
210     }
211
212     auto *r_ptr = &this->m_ptr->get_monrace();
213     if (this->m_ptr->is_pet() && r_ptr->kind_flags.has(MonsterKindType::UNIQUE) && (this->method == RaceBlowMethodType::EXPLODE)) {
214         this->method = RaceBlowMethodType::HIT;
215         this->damage_dice.num /= 10;
216     }
217
218     auto is_neighbor = distance(this->player_ptr->y, this->player_ptr->x, this->m_ptr->fy, this->m_ptr->fx) <= 1;
219     return this->player_ptr->playing && !this->player_ptr->is_dead && is_neighbor && !this->player_ptr->leaving;
220 }
221
222 /*!
223  * @brief モンスターから直接攻撃を1回受けた時の処理
224  * @return 対邪悪結界により攻撃が当たらなかったらFALSE、それ以外はTRUE
225  * @param this->player_ptr プレイヤーへの参照ポインタ
226  * @param monap_ptr モンスターからプレイヤーへの直接攻撃構造体への参照ポインタ
227  * @details 最大4 回/モンスター/ターン、このルーチンを通る
228  */
229 bool MonsterAttackPlayer::process_monster_attack_hit()
230 {
231     disturb(this->player_ptr, true, true);
232     if (this->effect_protecion_from_evil()) {
233         return false;
234     }
235
236     this->do_cut = 0;
237     this->do_stun = 0;
238     describe_monster_attack_method(this);
239     this->describe_silly_attacks();
240     this->obvious = true;
241     this->damage = this->damage_dice.roll();
242     if (this->explode) {
243         this->damage = 0;
244     }
245
246     switch_monster_blow_to_player(this->player_ptr, this);
247     this->select_cut_stun();
248     this->calc_player_cut();
249     this->process_player_stun();
250     this->monster_explode();
251     process_aura_counterattack(this->player_ptr, this);
252     return true;
253 }
254
255 /*!
256  * @brief 対邪悪結界が効いている状態で邪悪なモンスターから直接攻撃を受けた時の処理
257  * @return briefに書いた条件+確率が満たされたらTRUE、それ以外はFALSE
258  */
259 bool MonsterAttackPlayer::effect_protecion_from_evil()
260 {
261     auto *r_ptr = &this->m_ptr->get_monrace();
262     if ((this->player_ptr->protevil <= 0) || r_ptr->kind_flags.has_not(MonsterKindType::EVIL) || (this->player_ptr->lev < this->rlev) || ((randint0(100) + this->player_ptr->lev) <= 50)) {
263         return false;
264     }
265
266     if (is_original_ap_and_seen(this->player_ptr, this->m_ptr)) {
267         r_ptr->r_kind_flags.set(MonsterKindType::EVIL);
268     }
269
270 #ifdef JP
271     this->abbreviate ? msg_format("撃退した。") : msg_format("%s^は撃退された。", this->m_name);
272     this->abbreviate = 1; /* 2回目以降は省略 */
273 #else
274     msg_format("%s^ is repelled.", this->m_name);
275 #endif
276
277     return true;
278 }
279
280 void MonsterAttackPlayer::describe_silly_attacks()
281 {
282     if (this->act == nullptr) {
283         return;
284     }
285
286     if (this->do_silly_attack) {
287 #ifdef JP
288         this->abbreviate = -1;
289 #endif
290         this->act = rand_choice(silly_attacks);
291     }
292
293 #ifdef JP
294     if (this->abbreviate == 0) {
295         msg_format("%s^に%s", this->m_name, this->act);
296     } else if (this->abbreviate == 1) {
297         msg_format("%s", this->act);
298     } else {
299         /* if (this->abbreviate == -1) */
300         msg_format("%s^%s", this->m_name, this->act);
301     }
302
303     this->abbreviate = 1; /*2回目以降は省略 */
304 #else
305     msg_format("%s^ %s%s", this->m_name, this->act, this->do_silly_attack ? " you." : "");
306 #endif
307 }
308
309 /*!
310  * @brief 切り傷と朦朧が同時に発生した時、片方を無効にする
311  */
312 void MonsterAttackPlayer::select_cut_stun()
313 {
314     if ((this->do_cut == 0) || (this->do_stun == 0)) {
315         return;
316     }
317
318     if (one_in_(2)) {
319         this->do_cut = 0;
320     } else {
321         this->do_stun = 0;
322     }
323 }
324
325 void MonsterAttackPlayer::calc_player_cut()
326 {
327     if (this->do_cut == 0) {
328         return;
329     }
330
331     auto cut_plus = PlayerCut::get_accumulation(this->damage_dice.maxroll(), this->damage);
332     if (cut_plus > 0) {
333         (void)BadStatusSetter(this->player_ptr).mod_cut(cut_plus);
334     }
335 }
336
337 /*!
338  * @brief 朦朧を蓄積させる
339  * @param this->player_ptr プレイヤーへの参照ポインタ
340  * @param monap_ptr モンスター打撃への参照ポインタ
341  * @details
342  * 痛恨の一撃ならば朦朧蓄積ランクを1上げる.
343  * 2%の確率で朦朧蓄積ランクを1上げる.
344  * 肉体のパラメータが合計80を超える水準に強化されていたら朦朧蓄積ランクを1下げる.
345  */
346 void MonsterAttackPlayer::process_player_stun()
347 {
348     if (this->do_stun == 0) {
349         return;
350     }
351
352     auto total = this->damage_dice.maxroll();
353     auto accumulation_rank = PlayerStun::get_accumulation_rank(total, this->damage);
354     if (accumulation_rank == 0) {
355         return;
356     }
357
358     if ((total < this->damage) && (accumulation_rank <= 6)) {
359         accumulation_rank++;
360     }
361
362     if (one_in_(50)) {
363         accumulation_rank++;
364     }
365
366     auto str = this->stat_value(this->player_ptr->stat_cur[A_STR]);
367     auto dex = this->stat_value(this->player_ptr->stat_cur[A_DEX]);
368     auto con = this->stat_value(this->player_ptr->stat_cur[A_CON]);
369     auto is_powerful_body = str + dex + con > 80;
370     if (is_powerful_body) {
371         accumulation_rank--;
372     }
373
374     auto stun_plus = PlayerStun::get_accumulation(accumulation_rank);
375     if (stun_plus > 0) {
376         (void)BadStatusSetter(this->player_ptr).mod_stun(stun_plus);
377     }
378 }
379
380 void MonsterAttackPlayer::monster_explode()
381 {
382     if (!this->explode) {
383         return;
384     }
385
386     sound(SOUND_EXPLODE);
387     MonsterDamageProcessor mdp(this->player_ptr, this->m_idx, this->m_ptr->hp + 1, &this->fear, AttributeType::NONE);
388     if (mdp.mon_take_hit("")) {
389         this->blinked = false;
390         this->alive = false;
391     }
392 }
393
394 /*!
395  * @brief 一部の打撃種別の場合のみ、避けた旨のメッセージ表示と盾技能スキル向上を行う
396  * @param this->player_ptr プレイヤーへの参照ポインタ
397  * @param monap_ptr モンスターからプレイヤーへの直接攻撃構造体への参照ポインタ
398  */
399 void MonsterAttackPlayer::process_monster_attack_evasion()
400 {
401     switch (this->method) {
402     case RaceBlowMethodType::HIT:
403     case RaceBlowMethodType::TOUCH:
404     case RaceBlowMethodType::PUNCH:
405     case RaceBlowMethodType::KICK:
406     case RaceBlowMethodType::CLAW:
407     case RaceBlowMethodType::BITE:
408     case RaceBlowMethodType::STING:
409     case RaceBlowMethodType::SLASH:
410     case RaceBlowMethodType::BUTT:
411     case RaceBlowMethodType::CRUSH:
412     case RaceBlowMethodType::ENGULF:
413     case RaceBlowMethodType::CHARGE:
414         this->describe_attack_evasion();
415         this->gain_armor_exp();
416         this->damage = 0;
417         return;
418     default:
419         return;
420     }
421 }
422
423 void MonsterAttackPlayer::describe_attack_evasion()
424 {
425     if (!this->m_ptr->ml) {
426         return;
427     }
428
429     disturb(this->player_ptr, true, true);
430 #ifdef JP
431     auto is_suiken = any_bits(this->player_ptr->special_attack, ATTACK_SUIKEN);
432     if (this->abbreviate) {
433         msg_format("%sかわした。", is_suiken ? "奇妙な動きで" : "");
434     } else {
435         msg_format("%s%s^の攻撃をかわした。", is_suiken ? "奇妙な動きで" : "", this->m_name);
436     }
437
438     this->abbreviate = 1; /* 2回目以降は省略 */
439 #else
440     msg_format("%s^ misses you.", this->m_name);
441 #endif
442 }
443
444 void MonsterAttackPlayer::gain_armor_exp()
445 {
446     const auto o_ptr_mh = &this->player_ptr->inventory_list[INVEN_MAIN_HAND];
447     const auto o_ptr_sh = &this->player_ptr->inventory_list[INVEN_SUB_HAND];
448     if (!o_ptr_mh->is_protector() && !o_ptr_sh->is_protector()) {
449         return;
450     }
451
452     auto cur = this->player_ptr->skill_exp[PlayerSkillKindType::SHIELD];
453     auto max = class_skills_info[enum2i(this->player_ptr->pclass)].s_max[PlayerSkillKindType::SHIELD];
454     if (cur >= max) {
455         return;
456     }
457
458     auto *r_ptr = &this->m_ptr->get_monrace();
459     auto target_level = r_ptr->level;
460     short increment = 0;
461     if ((cur / 100) < target_level) {
462         auto addition = (cur / 100 + 15) < target_level ? (target_level - (cur / 100 + 15)) : 0;
463         increment += 1 + addition;
464     }
465
466     this->player_ptr->skill_exp[PlayerSkillKindType::SHIELD] = std::min<short>(max, cur + increment);
467     RedrawingFlagsUpdater::get_instance().set_flag(StatusRecalculatingFlag::BONUS);
468 }
469
470 /*!
471  * @brief モンスターの打撃情報を蓄積させる
472  * @param ap_cnt モンスターの打撃 N回目
473  * @details どの敵が何をしてきたか正しく認識できていなければ情報を蓄積しない.
474  * 非自明な類の打撃については、そのダメージが 0 ならば基本的に知識が増えない.
475  * 但し、既に一定以上の知識があれば常に知識が増える(何をされたのか察知できる).
476  */
477 void MonsterAttackPlayer::increase_blow_type_seen(const int ap_cnt)
478 {
479     if (!is_original_ap_and_seen(this->player_ptr, this->m_ptr) || this->do_silly_attack) {
480         return;
481     }
482
483     auto *r_ptr = &this->m_ptr->get_monrace();
484     if (!this->obvious && (this->damage == 0) && (r_ptr->r_blows[ap_cnt] <= 10)) {
485         return;
486     }
487
488     if (r_ptr->r_blows[ap_cnt] < MAX_UCHAR) {
489         r_ptr->r_blows[ap_cnt]++;
490     }
491 }
492
493 void MonsterAttackPlayer::postprocess_monster_blows()
494 {
495     SpellHex spell_hex(this->player_ptr);
496     spell_hex.store_vengeful_damage(this->get_damage);
497     spell_hex.eyes_on_eyes(this->m_idx, this->get_damage);
498     musou_counterattack(this->player_ptr, this);
499     this->process_thief_teleport(spell_hex);
500     auto *r_ptr = &this->m_ptr->get_monrace();
501     if (this->player_ptr->is_dead && (r_ptr->r_deaths < MAX_SHORT) && !this->player_ptr->current_floor_ptr->inside_arena) {
502         r_ptr->r_deaths++;
503     }
504
505     if (this->m_ptr->ml && this->fear && this->alive && !this->player_ptr->is_dead) {
506         sound(SOUND_FLEE);
507         msg_format(_("%s^は恐怖で逃げ出した!", "%s^ flees in terror!"), this->m_name);
508     }
509
510     PlayerClass(this->player_ptr).break_samurai_stance({ SamuraiStanceType::IAI });
511 }
512
513 void MonsterAttackPlayer::process_thief_teleport(const SpellHex &spell_hex)
514 {
515     if (!this->blinked || !this->alive || this->player_ptr->is_dead) {
516         return;
517     }
518
519     if (spell_hex.check_hex_barrier(this->m_idx, HEX_ANTI_TELE)) {
520         msg_print(_("泥棒は笑って逃げ...ようとしたがバリアに防がれた。", "The thief flees laughing...? But a magic barrier obstructs it."));
521     } else {
522         msg_print(_("泥棒は笑って逃げた!", "The thief flees laughing!"));
523         teleport_away(this->player_ptr, this->m_idx, MAX_PLAYER_SIGHT * 2 + 5, TELEPORT_SPONTANEOUS);
524     }
525 }