OSDN Git Service

054c728bb9f988d966156ab371c2f79864e55e2e
[hengbandforosx/hengbandosx.git] / src / mind / mind-ninja.cpp
1 #include "mind/mind-ninja.h"
2 #include "cmd-action/cmd-attack.h"
3 #include "cmd-item/cmd-throw.h"
4 #include "combat/combat-options-type.h"
5 #include "core/disturbance.h"
6 #include "core/player-redraw-types.h"
7 #include "effect/attribute-types.h"
8 #include "effect/effect-characteristics.h"
9 #include "effect/effect-processor.h"
10 #include "effect/spells-effect-util.h"
11 #include "floor/cave.h"
12 #include "floor/floor-object.h"
13 #include "floor/floor-util.h"
14 #include "floor/geometry.h"
15 #include "game-option/disturbance-options.h"
16 #include "grid/grid.h"
17 #include "inventory/inventory-slot-types.h"
18 #include "mind/mind-mirror-master.h"
19 #include "mind/mind-numbers.h"
20 #include "mind/mind-warrior.h"
21 #include "monster-race/monster-race.h"
22 #include "monster-race/race-flags-resistance.h"
23 #include "monster-race/race-indice-types.h"
24 #include "monster/monster-describer.h"
25 #include "monster/monster-status.h"
26 #include "monster/monster-update.h"
27 #include "object-enchant/trc-types.h"
28 #include "object/object-kind-hook.h"
29 #include "player-attack/player-attack-util.h"
30 #include "player-base/player-class.h"
31 #include "player-info/equipment-info.h"
32 #include "player-info/ninja-data-type.h"
33 #include "player-status/player-energy.h"
34 #include "player/attack-defense-types.h"
35 #include "player/player-status-flags.h"
36 #include "player/special-defense-types.h"
37 #include "spell-kind/spells-detection.h"
38 #include "spell-kind/spells-fetcher.h"
39 #include "spell-kind/spells-floor.h"
40 #include "spell-kind/spells-grid.h"
41 #include "spell-kind/spells-launcher.h"
42 #include "spell-kind/spells-lite.h"
43 #include "spell-kind/spells-perception.h"
44 #include "spell-kind/spells-teleport.h"
45 #include "spell/spells-status.h"
46 #include "status/action-setter.h"
47 #include "status/body-improvement.h"
48 #include "status/element-resistance.h"
49 #include "status/temporary-resistance.h"
50 #include "system/baseitem-info.h"
51 #include "system/floor-type-definition.h"
52 #include "system/grid-type-definition.h"
53 #include "system/item-entity.h"
54 #include "system/monster-entity.h"
55 #include "system/monster-race-info.h"
56 #include "system/player-type-definition.h"
57 #include "system/redrawing-flags-updater.h"
58 #include "system/terrain-type-definition.h"
59 #include "target/projection-path-calculator.h"
60 #include "target/target-checker.h"
61 #include "target/target-getter.h"
62 #include "timed-effect/player-blindness.h"
63 #include "timed-effect/player-confusion.h"
64 #include "timed-effect/player-hallucination.h"
65 #include "timed-effect/player-paralysis.h"
66 #include "timed-effect/player-stun.h"
67 #include "timed-effect/timed-effects.h"
68 #include "util/bit-flags-calculator.h"
69 #include "view/display-messages.h"
70
71 /*!
72  * @brief 変わり身処理
73  * @param player_ptr プレイヤーへの参照ポインタ
74  * @param success 判定成功上の処理ならばTRUE
75  * @return 作用が実際にあった場合TRUEを返す
76  */
77 bool kawarimi(PlayerType *player_ptr, bool success)
78 {
79     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
80     if (!ninja_data || !ninja_data->kawarimi) {
81         return false;
82     }
83
84     ItemEntity forge;
85     auto *q_ptr = &forge;
86     if (player_ptr->is_dead) {
87         return false;
88     }
89
90     const auto effects = player_ptr->effects();
91     const auto is_confused = effects->confusion()->is_confused();
92     const auto is_blind = effects->blindness()->is_blind();
93     const auto is_hallucinated = effects->hallucination()->is_hallucinated();
94     const auto is_paralyzed = effects->paralysis()->is_paralyzed();
95     if (is_confused || is_blind || is_paralyzed || is_hallucinated) {
96         return false;
97     }
98
99     if (effects->stun()->current() > randint0(200)) {
100         return false;
101     }
102
103     auto &rfu = RedrawingFlagsUpdater::get_instance();
104     if (!success && one_in_(3)) {
105         msg_print(_("変わり身失敗!逃げられなかった。", "Kawarimi failed! You couldn't run away."));
106         ninja_data->kawarimi = false;
107         rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
108         return false;
109     }
110
111     POSITION y = player_ptr->y;
112     POSITION x = player_ptr->x;
113
114     teleport_player(player_ptr, 10 + randint1(90), TELEPORT_SPONTANEOUS);
115     q_ptr->wipe();
116     const int sv_wooden_statue = 0;
117     q_ptr->prep(lookup_baseitem_id({ ItemKindType::STATUE, sv_wooden_statue }));
118
119     q_ptr->pval = enum2i(MonsterRaceId::NINJA);
120     (void)drop_near(player_ptr, q_ptr, -1, y, x);
121
122     if (success) {
123         msg_print(_("攻撃を受ける前に素早く身をひるがえした。", "You have turned around just before the attack hit you."));
124     } else {
125         msg_print(_("変わり身失敗!攻撃を受けてしまった。", "Kawarimi failed! You are hit by the attack."));
126     }
127
128     ninja_data->kawarimi = false;
129     rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
130     return true;
131 }
132
133 /*!
134  * @brief 入身処理 / "Rush Attack" routine for Samurai or Ninja
135  * @param player_ptr プレイヤーへの参照ポインタ
136  * @param mdeath 目標モンスターが死亡したかを返す
137  * @return 作用が実際にあった場合TRUEを返す /  Return value is for checking "done"
138  */
139 bool rush_attack(PlayerType *player_ptr, bool *mdeath)
140 {
141     if (mdeath) {
142         *mdeath = false;
143     }
144
145     project_length = 5;
146     DIRECTION dir;
147     if (!get_aim_dir(player_ptr, &dir)) {
148         return false;
149     }
150
151     int tx = player_ptr->x + project_length * ddx[dir];
152     int ty = player_ptr->y + project_length * ddy[dir];
153
154     if ((dir == 5) && target_okay(player_ptr)) {
155         tx = target_col;
156         ty = target_row;
157     }
158
159     int tm_idx = 0;
160     auto *floor_ptr = player_ptr->current_floor_ptr;
161     if (in_bounds(floor_ptr, ty, tx)) {
162         tm_idx = floor_ptr->grid_array[ty][tx].m_idx;
163     }
164
165     projection_path path_g(player_ptr, project_length, player_ptr->y, player_ptr->x, ty, tx, PROJECT_STOP | PROJECT_KILL);
166     project_length = 0;
167     if (path_g.path_num() == 0) {
168         return true;
169     }
170
171     ty = player_ptr->y;
172     tx = player_ptr->x;
173     bool tmp_mdeath = false;
174     bool moved = false;
175     for (const auto &[ny, nx] : path_g) {
176         MonsterEntity *m_ptr;
177
178         if (is_cave_empty_bold(player_ptr, ny, nx) && player_can_enter(player_ptr, floor_ptr->grid_array[ny][nx].feat, 0)) {
179             ty = ny;
180             tx = nx;
181             continue;
182         }
183
184         if (!floor_ptr->grid_array[ny][nx].m_idx) {
185             if (tm_idx) {
186                 msg_print(_("失敗!", "Failed!"));
187             } else {
188                 msg_print(_("ここには入身では入れない。", "You can't move to that place."));
189             }
190
191             break;
192         }
193
194         if (!player_bold(player_ptr, ty, tx)) {
195             teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
196         }
197         update_monster(player_ptr, floor_ptr->grid_array[ny][nx].m_idx, true);
198
199         m_ptr = &floor_ptr->m_list[floor_ptr->grid_array[ny][nx].m_idx];
200         if (tm_idx != floor_ptr->grid_array[ny][nx].m_idx) {
201 #ifdef JP
202             msg_format("%s%sが立ちふさがっている!", tm_idx ? "別の" : "", m_ptr->ml ? "モンスター" : "何か");
203 #else
204             msg_format("There is %s in the way!", m_ptr->ml ? (tm_idx ? "another monster" : "a monster") : "someone");
205 #endif
206         } else if (!player_bold(player_ptr, ty, tx)) {
207             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
208             msg_format(_("素早く%sの懐に入り込んだ!", "You quickly jump in and attack %s!"), m_name.data());
209         }
210
211         if (!player_bold(player_ptr, ty, tx)) {
212             teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
213         }
214         moved = true;
215         tmp_mdeath = do_cmd_attack(player_ptr, ny, nx, HISSATSU_NYUSIN);
216
217         break;
218     }
219
220     if (!moved && !player_bold(player_ptr, ty, tx)) {
221         teleport_player_to(player_ptr, ty, tx, TELEPORT_NONMAGICAL);
222     }
223
224     if (mdeath) {
225         *mdeath = tmp_mdeath;
226     }
227     return true;
228 }
229
230 /*!
231  * @brief 盗賊と忍者における不意打ち
232  * @param player_ptr プレイヤーへの参照ポインタ
233  * @param pa_ptr 直接攻撃構造体への参照ポインタ
234  */
235 void process_surprise_attack(PlayerType *player_ptr, player_attack_type *pa_ptr)
236 {
237     auto *r_ptr = &monraces_info[pa_ptr->m_ptr->r_idx];
238     if (!has_melee_weapon(player_ptr, enum2i(INVEN_MAIN_HAND) + pa_ptr->hand) || player_ptr->is_icky_wield[pa_ptr->hand]) {
239         return;
240     }
241
242     int tmp = player_ptr->lev * 6 + (player_ptr->skill_stl + 10) * 4;
243     if (player_ptr->monlite && (pa_ptr->mode != HISSATSU_NYUSIN)) {
244         tmp /= 3;
245     }
246     if (has_aggravate(player_ptr)) {
247         tmp /= 2;
248     }
249     if (r_ptr->level > (player_ptr->lev * player_ptr->lev / 20 + 10)) {
250         tmp /= 3;
251     }
252
253     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
254     if (pa_ptr->m_ptr->is_asleep() && pa_ptr->m_ptr->ml) {
255         /* Can't backstab creatures that we can't see, right? */
256         pa_ptr->backstab = true;
257     } else if ((ninja_data && ninja_data->s_stealth) && (randint0(tmp) > (r_ptr->level + 20)) &&
258                pa_ptr->m_ptr->ml && !r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_ALL)) {
259         pa_ptr->surprise_attack = true;
260     } else if (pa_ptr->m_ptr->is_fearful() && pa_ptr->m_ptr->ml) {
261         pa_ptr->stab_fleeing = true;
262     }
263 }
264
265 void print_surprise_attack(player_attack_type *pa_ptr)
266 {
267     if (pa_ptr->backstab) {
268         msg_format(_("あなたは冷酷にも眠っている無力な%sを突き刺した!", "You cruelly stab the helpless, sleeping %s!"), pa_ptr->m_name);
269     } else if (pa_ptr->surprise_attack) {
270         msg_format(_("不意を突いて%sに強烈な一撃を喰らわせた!", "You make surprise attack, and hit %s with a powerful blow!"), pa_ptr->m_name);
271     } else if (pa_ptr->stab_fleeing) {
272         msg_format(_("逃げる%sを背中から突き刺した!", "You backstab the fleeing %s!"), pa_ptr->m_name);
273     } else if (!pa_ptr->monk_attack) {
274         msg_format(_("%sを攻撃した。", "You hit %s."), pa_ptr->m_name);
275     }
276 }
277
278 /*!
279  * @brief 盗賊と忍者における不意打ちのダメージ計算
280  * @param player_ptr プレイヤーへの参照ポインタ
281  * @param pa_ptr 直接攻撃構造体への参照ポインタ
282  */
283 void calc_surprise_attack_damage(PlayerType *player_ptr, player_attack_type *pa_ptr)
284 {
285     if (pa_ptr->backstab) {
286         pa_ptr->attack_damage *= (3 + (player_ptr->lev / 20));
287         return;
288     }
289
290     if (pa_ptr->surprise_attack) {
291         pa_ptr->attack_damage = pa_ptr->attack_damage * (5 + (player_ptr->lev * 2 / 25)) / 2;
292         return;
293     }
294
295     if (pa_ptr->stab_fleeing) {
296         pa_ptr->attack_damage = (3 * pa_ptr->attack_damage) / 2;
297     }
298 }
299
300 /*!
301  * @brief 速駆け処理
302  * @param player_ptr プレイヤーへの参照ポインタ
303  * @return 常にTRUE
304  */
305 bool hayagake(PlayerType *player_ptr)
306 {
307     PlayerEnergy energy(player_ptr);
308     if (player_ptr->action == ACTION_HAYAGAKE) {
309         set_action(player_ptr, ACTION_NONE);
310         energy.reset_player_turn();
311         return true;
312     }
313
314     auto *g_ptr = &player_ptr->current_floor_ptr->grid_array[player_ptr->y][player_ptr->x];
315     auto *f_ptr = &terrains_info[g_ptr->feat];
316
317     if (f_ptr->flags.has_not(TerrainCharacteristics::PROJECT) || (!player_ptr->levitation && f_ptr->flags.has(TerrainCharacteristics::DEEP))) {
318         msg_print(_("ここでは素早く動けない。", "You cannot run in here."));
319     } else {
320         set_action(player_ptr, ACTION_HAYAGAKE);
321     }
322
323     energy.reset_player_turn();
324     return true;
325 }
326
327 /*!
328  * @brief 超隠密状態をセットする
329  * @param set TRUEならば超隠密状態になる。
330  * @return ステータスに影響を及ぼす変化があった場合TRUEを返す。
331  */
332 bool set_superstealth(PlayerType *player_ptr, bool set)
333 {
334     bool notice = false;
335
336     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
337     if (!ninja_data || player_ptr->is_dead) {
338         return false;
339     }
340
341     if (set) {
342         if (!ninja_data->s_stealth) {
343             if (player_ptr->current_floor_ptr->grid_array[player_ptr->y][player_ptr->x].info & CAVE_MNLT) {
344                 msg_print(_("敵の目から薄い影の中に覆い隠された。", "You are mantled in weak shadow from ordinary eyes."));
345                 player_ptr->monlite = player_ptr->old_monlite = true;
346             } else {
347                 msg_print(_("敵の目から影の中に覆い隠された!", "You are mantled in shadow from ordinary eyes!"));
348                 player_ptr->monlite = player_ptr->old_monlite = false;
349             }
350
351             notice = true;
352             ninja_data->s_stealth = true;
353         }
354     } else {
355         if (ninja_data->s_stealth) {
356             msg_print(_("再び敵の目にさらされるようになった。", "You are exposed to common sight once more."));
357             notice = true;
358             ninja_data->s_stealth = false;
359         }
360     }
361
362     if (!notice) {
363         return false;
364     }
365
366     RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
367     if (disturb_state) {
368         disturb(player_ptr, false, false);
369     }
370
371     return true;
372 }
373
374 /*!
375  * @brief 忍術の発動 /
376  * do_cmd_cast calls this function if the player's class is 'ninja'.
377  * @param player_ptr プレイヤーへの参照ポインタ
378  * @param spell 発動する特殊技能のID
379  * @return 処理を実行したらTRUE、キャンセルした場合FALSEを返す。
380  */
381 bool cast_ninja_spell(PlayerType *player_ptr, MindNinjaType spell)
382 {
383     POSITION x = 0, y = 0;
384     DIRECTION dir;
385     PLAYER_LEVEL plev = player_ptr->lev;
386     auto ninja_data = PlayerClass(player_ptr).get_specific_data<ninja_data_type>();
387     switch (spell) {
388     case MindNinjaType::DARKNESS_CREATION:
389         (void)unlite_area(player_ptr, 0, 3);
390         break;
391     case MindNinjaType::DETECT_NEAR:
392         if (plev > 44) {
393             wiz_lite(player_ptr, true);
394         }
395
396         detect_monsters_normal(player_ptr, DETECT_RAD_DEFAULT);
397         if (plev > 4) {
398             detect_traps(player_ptr, DETECT_RAD_DEFAULT, true);
399             detect_doors(player_ptr, DETECT_RAD_DEFAULT);
400             detect_stairs(player_ptr, DETECT_RAD_DEFAULT);
401         }
402
403         if (plev > 14) {
404             detect_objects_normal(player_ptr, DETECT_RAD_DEFAULT);
405         }
406
407         break;
408     case MindNinjaType::HIDE_LEAVES:
409         teleport_player(player_ptr, 10, TELEPORT_SPONTANEOUS);
410         break;
411     case MindNinjaType::KAWARIMI:
412         if (ninja_data && !ninja_data->kawarimi) {
413             msg_print(_("敵の攻撃に対して敏感になった。", "You are now prepared to evade any attacks."));
414             ninja_data->kawarimi = true;
415             RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
416         }
417
418         break;
419     case MindNinjaType::ABSCONDING:
420         teleport_player(player_ptr, player_ptr->lev * 5, TELEPORT_SPONTANEOUS);
421         break;
422     case MindNinjaType::HIT_AND_AWAY:
423         if (!hit_and_away(player_ptr)) {
424             return false;
425         }
426
427         break;
428     case MindNinjaType::BIND_MONSTER:
429         if (!get_aim_dir(player_ptr, &dir)) {
430             return false;
431         }
432
433         (void)stasis_monster(player_ptr, dir);
434         break;
435     case MindNinjaType::ANCIENT_KNOWLEDGE:
436         return ident_spell(player_ptr, false);
437     case MindNinjaType::FLOATING:
438         set_tim_levitation(player_ptr, randint1(20) + 20, false);
439         break;
440     case MindNinjaType::HIDE_FLAMES:
441         fire_ball(player_ptr, AttributeType::FIRE, 0, 50 + plev, plev / 10 + 2);
442         teleport_player(player_ptr, 30, TELEPORT_SPONTANEOUS);
443         set_oppose_fire(player_ptr, (TIME_EFFECT)plev, false);
444         break;
445     case MindNinjaType::NYUSIN:
446         return rush_attack(player_ptr, nullptr);
447     case MindNinjaType::SYURIKEN_SPREADING: {
448         for (int i = 0; i < 8; i++) {
449             OBJECT_IDX slot;
450
451             for (slot = 0; slot < INVEN_PACK; slot++) {
452                 if (player_ptr->inventory_list[slot].bi_key.tval() == ItemKindType::SPIKE) {
453                     break;
454                 }
455             }
456
457             if (slot == INVEN_PACK) {
458                 if (!i) {
459                     msg_print(_("くさびを持っていない。", "You have no Iron Spikes."));
460                 } else {
461                     msg_print(_("くさびがなくなった。", "You have no more Iron Spikes."));
462                 }
463
464                 return false;
465             }
466
467             (void)ThrowCommand(player_ptr).do_cmd_throw(1, false, slot);
468             PlayerEnergy(player_ptr).set_player_turn_energy(100);
469         }
470
471         break;
472     }
473     case MindNinjaType::CHAIN_HOOK:
474         (void)fetch_monster(player_ptr);
475         break;
476     case MindNinjaType::SMOKE_BALL:
477         if (!get_aim_dir(player_ptr, &dir)) {
478             return false;
479         }
480
481         fire_ball(player_ptr, AttributeType::OLD_CONF, dir, plev * 3, 3);
482         break;
483     case MindNinjaType::SWAP_POSITION:
484         project_length = -1;
485         if (!get_aim_dir(player_ptr, &dir)) {
486             project_length = 0;
487             return false;
488         }
489
490         project_length = 0;
491         (void)teleport_swap(player_ptr, dir);
492         break;
493     case MindNinjaType::EXPLOSIVE_RUNE:
494         create_rune_explosion(player_ptr, player_ptr->y, player_ptr->x);
495         break;
496     case MindNinjaType::HIDE_MUD:
497         (void)set_pass_wall(player_ptr, randint1(plev / 2) + plev / 2, false);
498         set_oppose_acid(player_ptr, (TIME_EFFECT)plev, false);
499         break;
500     case MindNinjaType::HIDE_MIST:
501         fire_ball(player_ptr, AttributeType::POIS, 0, 75 + plev * 2 / 3, plev / 5 + 2);
502         fire_ball(player_ptr, AttributeType::HYPODYNAMIA, 0, 75 + plev * 2 / 3, plev / 5 + 2);
503         fire_ball(player_ptr, AttributeType::CONFUSION, 0, 75 + plev * 2 / 3, plev / 5 + 2);
504         teleport_player(player_ptr, 30, TELEPORT_SPONTANEOUS);
505         break;
506     case MindNinjaType::PURGATORY_FLAME: {
507         int num = damroll(3, 9);
508         for (int k = 0; k < num; k++) {
509             AttributeType typ = one_in_(2) ? AttributeType::FIRE : one_in_(3) ? AttributeType::NETHER
510                                                                               : AttributeType::PLASMA;
511             int attempts = 1000;
512             while (attempts--) {
513                 scatter(player_ptr, &y, &x, player_ptr->y, player_ptr->x, 4, PROJECT_NONE);
514                 if (!player_bold(player_ptr, y, x)) {
515                     break;
516                 }
517             }
518
519             project(player_ptr, 0, 0, y, x, damroll(6 + plev / 8, 10), typ, (PROJECT_BEAM | PROJECT_THRU | PROJECT_GRID | PROJECT_KILL));
520         }
521
522         break;
523     }
524     case MindNinjaType::ALTER_EGO:
525         set_multishadow(player_ptr, 6 + randint1(6), false);
526         break;
527     default:
528         msg_print(_("なに?", "Zap?"));
529         break;
530     }
531     return true;
532 }