1 #include "effect/effect-processor.h"
2 #include "core/stuff-handler.h"
3 #include "effect/attribute-types.h"
4 #include "effect/effect-characteristics.h"
5 #include "effect/effect-feature.h"
6 #include "effect/effect-item.h"
7 #include "effect/effect-monster.h"
8 #include "effect/effect-player.h"
9 #include "effect/spells-effect-util.h"
10 #include "floor/cave.h"
11 #include "floor/geometry.h"
12 #include "floor/line-of-sight.h"
13 #include "game-option/map-screen-options.h"
14 #include "game-option/special-options.h"
15 #include "grid/feature-flag-types.h"
16 #include "grid/grid.h"
17 #include "io/cursor.h"
18 #include "io/screen-util.h"
19 #include "main/sound-definitions-table.h"
20 #include "main/sound-of-music.h"
21 #include "monster-race/monster-race.h"
22 #include "monster-race/race-indice-types.h"
23 #include "monster/monster-describer.h"
24 #include "monster/monster-description-types.h"
25 #include "monster/monster-info.h"
26 #include "monster/monster-util.h"
27 #include "pet/pet-fall-off.h"
28 #include "player/player-status.h"
29 #include "spell-class/spells-mirror-master.h"
30 #include "spell/range-calc.h"
31 #include "system/angband-system.h"
32 #include "system/floor-type-definition.h"
33 #include "system/grid-type-definition.h"
34 #include "system/monster-entity.h"
35 #include "system/monster-race-info.h"
36 #include "system/player-type-definition.h"
37 #include "target/projection-path-calculator.h"
38 #include "timed-effect/player-blindness.h"
39 #include "timed-effect/player-hallucination.h"
40 #include "timed-effect/timed-effects.h"
41 #include "util/bit-flags-calculator.h"
42 #include "view/display-messages.h"
45 * @brief 汎用的なビーム/ボルト/ボール系処理のルーチン Generic
46 * "beam"/"bolt"/"ball" projection routine.
47 * @param src_idx 魔法を発動したモンスター(0ならばプレイヤー) / Index of "source"
48 * monster (zero for "player")
49 * @param rad 効果半径(ビーム/ボルト = 0 / ボール = 1以上) / Radius of explosion
50 * (0 = beam/bolt, 1 to 9 = ball)
51 * @param target_y 目標Y座標 / Target y location (or location to travel "towards")
52 * @param target_x 目標X座標 / Target x location (or location to travel "towards")
53 * @param dam 基本威力 / Base damage roll to apply to affected monsters (or
55 * @param typ 効果属性 / Type of damage to apply to monsters (and objects)
56 * @param flag 効果フラグ / Extra bit flags (see PROJECT_xxxx)
57 * @param monspell 効果元のモンスター魔法ID
58 * @todo 似たような処理が山ほど並んでいる、何とかならないものか
59 * @todo 引数にそのまま再代入していてカオスすぎる。直すのは簡単ではない
61 ProjectResult project(PlayerType *player_ptr, const MONSTER_IDX src_idx, POSITION rad, const POSITION target_y, const POSITION target_x, const int dam,
62 const AttributeType typ, BIT_FLAGS flag, std::optional<CapturedMonsterType *> cap_mon_ptr)
69 bool old_hide = false;
77 monster_target_y = player_ptr->y;
78 monster_target_x = player_ptr->x;
82 if (any_bits(flag, PROJECT_JUMP)) {
85 } else if (!is_monster(src_idx)) {
88 } else if (is_monster(src_idx)) {
89 x1 = player_ptr->current_floor_ptr->m_list[src_idx].fx;
90 y1 = player_ptr->current_floor_ptr->m_list[src_idx].fy;
98 if (flag & (PROJECT_THRU)) {
99 if ((x1 == x2) && (y1 == y2)) {
100 flag &= ~(PROJECT_THRU);
104 if (flag & (PROJECT_BREATH)) {
106 if (flag & PROJECT_HIDE) {
109 flag |= PROJECT_HIDE;
112 for (auto dist = 0; dist < 32; dist++) {
116 if (flag & (PROJECT_BEAM)) {
123 case AttributeType::LITE:
124 case AttributeType::LITE_WEAK:
125 if (breath || (flag & PROJECT_BEAM)) {
126 flag |= (PROJECT_LOS);
129 case AttributeType::DISINTEGRATE:
130 flag |= (PROJECT_GRID);
131 if (breath || (flag & PROJECT_BEAM)) {
132 flag |= (PROJECT_DISI);
139 /* Calculate the projection path */
140 const auto &system = AngbandSystem::get_instance();
141 projection_path path_g(player_ptr, (project_length ? project_length : system.get_max_range()), y1, x1, y2, x2, flag);
142 handle_stuff(player_ptr);
148 bool see_s_msg = true;
149 const auto is_blind = player_ptr->effects()->blindness()->is_blind();
150 auto &floor = *player_ptr->current_floor_ptr;
151 for (const auto &[ny, nx] : path_g) {
152 const Pos2D pos(ny, nx);
153 if (flag & PROJECT_DISI) {
154 if (cave_stop_disintegration(player_ptr->current_floor_ptr, ny, nx) && (rad > 0)) {
157 } else if (flag & PROJECT_LOS) {
158 if (!cave_los_bold(&floor, ny, nx) && (rad > 0)) {
162 if (!cave_has_flag_bold(&floor, ny, nx, TerrainCharacteristics::PROJECT) && (rad > 0)) {
166 if (flag & (PROJECT_BEAM)) {
172 if (delay_factor > 0) {
173 if (!is_blind && !(flag & (PROJECT_HIDE | PROJECT_FAST))) {
174 if (panel_contains(ny, nx) && floor.has_los(pos)) {
175 print_bolt_pict(player_ptr, oy, ox, ny, nx, typ);
176 move_cursor_relative(ny, nx);
178 term_xtra(TERM_XTRA_DELAY, delay_factor);
179 lite_spot(player_ptr, ny, nx);
181 if (flag & (PROJECT_BEAM)) {
182 print_bolt_pict(player_ptr, ny, nx, ny, nx, typ);
187 term_xtra(TERM_XTRA_DELAY, delay_factor);
200 if (breath && !path_n) {
203 flag &= ~(PROJECT_HIDE);
211 POSITION gm_rad = rad;
212 /* If we found a "target", explode there */
213 if (path_n <= system.get_max_range()) {
214 if ((flag & (PROJECT_BEAM)) && (grids > 0)) {
219 * Create a conical breath attack
228 flag &= ~(PROJECT_HIDE);
229 breath_shape(player_ptr, path_g, path_n, &grids, gx, gy, gm, &gm_rad, rad, y1, x1, by, bx, typ);
231 for (auto dist = 0; dist <= rad; dist++) {
232 for (auto y = by - dist; y <= by + dist; y++) {
233 for (auto x = bx - dist; x <= bx + dist; x++) {
234 if (!in_bounds2(player_ptr->current_floor_ptr, y, x)) {
237 if (distance(by, bx, y, x) != dist) {
242 case AttributeType::LITE:
243 case AttributeType::LITE_WEAK:
244 if (!los(player_ptr, by, bx, y, x)) {
248 case AttributeType::DISINTEGRATE:
249 if (!in_disintegration_range(player_ptr->current_floor_ptr, by, bx, y, x)) {
254 if (!projectable(player_ptr, by, bx, y, x)) {
266 gm[dist + 1] = grids;
275 if (!is_blind && !(flag & (PROJECT_HIDE)) && (delay_factor > 0)) {
277 for (int t = 0; t <= gm_rad; t++) {
278 for (int i = gm[t]; i < gm[t + 1]; i++) {
279 const Pos2D pos(gy[i], gx[i]);
280 if (panel_contains(pos.y, pos.x) && floor.has_los(pos)) {
282 print_bolt_pict(player_ptr, pos.y, pos.x, pos.y, pos.x, typ);
286 move_cursor_relative(by, bx);
288 if (visual || drawn) {
289 term_xtra(TERM_XTRA_DELAY, delay_factor);
294 for (int i = 0; i < grids; i++) {
295 const Pos2D pos(gy[i], gx[i]);
296 if (panel_contains(pos.y, pos.x) && floor.has_los(pos)) {
297 lite_spot(player_ptr, pos.y, pos.x);
301 move_cursor_relative(by, bx);
306 update_creature(player_ptr);
308 if (flag & PROJECT_KILL) {
309 see_s_msg = is_monster(src_idx) ? is_seen(player_ptr, &player_ptr->current_floor_ptr->m_list[src_idx])
310 : (is_player(src_idx) ? true : (player_can_see_bold(player_ptr, y1, x1) && projectable(player_ptr, player_ptr->y, player_ptr->x, y1, x1)));
313 if (flag & (PROJECT_GRID)) {
315 for (int i = 0; i < grids; i++) {
316 if (gm[dist + 1] == i) {
322 int d = dist_to_line(y, x, y1, x1, by, bx);
323 if (affect_feature(player_ptr, src_idx, d, y, x, dam, typ)) {
327 if (affect_feature(player_ptr, src_idx, dist, y, x, dam, typ)) {
334 update_creature(player_ptr);
335 if (flag & (PROJECT_ITEM)) {
337 for (int i = 0; i < grids; i++) {
338 if (gm[dist + 1] == i) {
345 int d = dist_to_line(y, x, y1, x1, by, bx);
346 if (affect_item(player_ptr, src_idx, d, y, x, dam, typ)) {
350 if (affect_item(player_ptr, src_idx, dist, y, x, dam, typ)) {
357 if (flag & (PROJECT_KILL)) {
362 for (int i = 0; i < grids; i++) {
364 if (gm[dist + 1] == i) {
368 const Pos2D pos(gy[i], gx[i]);
369 const auto &grid = floor.get_grid(pos);
371 auto *m_ptr = &floor.m_list[grid.m_idx];
372 MonsterRaceInfo *ref_ptr = &m_ptr->get_monrace();
373 if ((flag & PROJECT_REFLECTABLE) && grid.m_idx && ref_ptr->misc_flags.has(MonsterMiscType::REFLECTING) && ((grid.m_idx != player_ptr->riding) || !(flag & PROJECT_PLAYER)) && (!src_idx || path_n > 1) && !one_in_(10)) {
376 int max_attempts = 10;
378 t_y = y1 - 1 + randint1(3);
379 t_x = x1 - 1 + randint1(3);
381 } while (max_attempts && in_bounds2u(player_ptr->current_floor_ptr, t_y, t_x) && !projectable(player_ptr, pos.y, pos.x, t_y, t_x));
383 if (max_attempts < 1) {
388 if (is_seen(player_ptr, m_ptr)) {
389 sound(SOUND_REFLECT);
390 if ((m_ptr->r_idx == MonsterRaceId::KENSHIROU) || (m_ptr->r_idx == MonsterRaceId::RAOU)) {
391 msg_print(_("「北斗神拳奥義・二指真空把!」", "The attack bounces!"));
392 } else if (m_ptr->r_idx == MonsterRaceId::DIO) {
393 msg_print(_("ディオ・ブランドーは指一本で攻撃を弾き返した!", "The attack bounces!"));
395 msg_print(_("攻撃は跳ね返った!", "The attack bounces!"));
397 } else if (!is_monster(src_idx)) {
398 sound(SOUND_REFLECT);
401 if (is_original_ap_and_seen(player_ptr, m_ptr)) {
402 ref_ptr->r_misc_flags.set(MonsterMiscType::REFLECTING);
405 if (player_ptr->is_located_at(pos) || one_in_(2)) {
406 flag &= ~(PROJECT_PLAYER);
408 flag |= PROJECT_PLAYER;
411 project(player_ptr, grid.m_idx, 0, t_y, t_x, dam, typ, flag);
416 /* Find the closest point in the blast */
418 effective_dist = dist_to_line(pos.y, pos.x, y1, x1, by, bx);
420 effective_dist = dist;
423 if (player_ptr->riding && player_ptr->is_located_at(pos)) {
424 if (flag & PROJECT_PLAYER) {
425 if (flag & (PROJECT_BEAM | PROJECT_REFLECTABLE | PROJECT_AIMED)) {
427 * A beam or bolt is well aimed
429 * So don't affects the mount.
434 * The spell is not well aimed,
435 * So partly affect the mount too.
442 * This grid is the original target.
443 * Or aimed on your horse.
445 else if (((pos.y == y2) && (pos.x == x2)) || (flag & PROJECT_AIMED)) {
446 /* Hit the mount with full damage */
450 * Otherwise this grid is not the
451 * original target, it means that line
452 * of fire is obstructed by this
456 * A beam or bolt will hit either
457 * player or mount. Choose randomly.
459 else if (flag & (PROJECT_BEAM | PROJECT_REFLECTABLE)) {
461 /* Hit the mount with full damage */
463 flag |= PROJECT_PLAYER;
469 * The spell is not well aimed, so
470 * partly affect both player and
478 if (affect_monster(player_ptr, src_idx, effective_dist, pos.y, pos.x, dam, typ, flag, see_s_msg, cap_mon_ptr)) {
482 /* Player affected one monster (without "jumping") */
483 if (!src_idx && (project_m_n == 1) && none_bits(flag, PROJECT_JUMP)) {
484 const Pos2D pos_project(project_m_y, project_m_x);
485 const auto &grid = floor.get_grid(pos_project);
486 if (grid.has_monster()) {
487 auto &monster = floor.m_list[grid.m_idx];
489 if (!player_ptr->effects()->hallucination()->is_hallucinated()) {
490 monster_race_track(player_ptr, monster.ap_r_idx);
493 health_track(player_ptr, grid.m_idx);
499 if (flag & (PROJECT_KILL)) {
501 for (int i = 0; i < grids; i++) {
503 if (gm[dist + 1] == i) {
507 const Pos2D pos(gy[i], gx[i]);
508 if (!player_ptr->is_located_at(pos)) {
512 /* Find the closest point in the blast */
514 effective_dist = dist_to_line(pos.y, pos.x, y1, x1, by, bx);
516 effective_dist = dist;
519 if (player_ptr->riding) {
520 if (flag & PROJECT_PLAYER) {
521 /* Hit the player with full damage */
525 * Hack -- When this grid was not the
526 * original target, a beam or bolt
527 * would hit either player or mount,
528 * and should be choosen randomly.
530 * But already choosen to hit the
531 * mount at this point.
533 * Or aimed on your horse.
535 else if (flag & (PROJECT_BEAM | PROJECT_REFLECTABLE | PROJECT_AIMED)) {
537 * A beam or bolt is well aimed
539 * So don't affects the player.
544 * The spell is not well aimed,
545 * So partly affect the player too.
551 std::string who_name;
552 if (is_monster(src_idx)) {
553 who_name = monster_desc(player_ptr, &floor.m_list[src_idx], MD_WRONGDOER_NAME);
556 if (affect_player(src_idx, player_ptr, who_name.data(), effective_dist, pos.y, pos.x, dam, typ, flag, project)) {
558 res.affected_player = true;
563 if (player_ptr->riding) {
564 const auto m_name = monster_desc(player_ptr, &floor.m_list[player_ptr->riding], 0);
565 if (rakubadam_m > 0) {
566 if (process_fall_off_horse(player_ptr, rakubadam_m, false)) {
567 msg_format(_("%s^に振り落とされた!", "%s^ has thrown you off!"), m_name.data());
571 if (player_ptr->riding && rakubadam_p > 0) {
572 if (process_fall_off_horse(player_ptr, rakubadam_p, false)) {
573 msg_format(_("%s^から落ちてしまった!", "You have fallen from %s."), m_name.data());