OSDN Git Service

[Refactor] Grid::has_monster()の定義
[hengbandforosx/hengbandosx.git] / src / effect / effect-processor.cpp
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"
43
44 /*!
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
54  * player)
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 引数にそのまま再代入していてカオスすぎる。直すのは簡単ではない
60  */
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)
63 {
64     POSITION y1;
65     POSITION x1;
66     POSITION y2;
67     POSITION x2;
68     bool breath = false;
69     bool old_hide = false;
70     int path_n = 0;
71     int grids = 0;
72     POSITION gx[1024]{};
73     POSITION gy[1024]{};
74     POSITION gm[32]{};
75     rakubadam_p = 0;
76     rakubadam_m = 0;
77     monster_target_y = player_ptr->y;
78     monster_target_x = player_ptr->x;
79
80     ProjectResult res;
81
82     if (any_bits(flag, PROJECT_JUMP)) {
83         x1 = target_x;
84         y1 = target_y;
85     } else if (!is_monster(src_idx)) {
86         x1 = player_ptr->x;
87         y1 = player_ptr->y;
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;
91     } else {
92         x1 = target_x;
93         y1 = target_y;
94     }
95     y2 = target_y;
96     x2 = target_x;
97
98     if (flag & (PROJECT_THRU)) {
99         if ((x1 == x2) && (y1 == y2)) {
100             flag &= ~(PROJECT_THRU);
101         }
102     }
103
104     if (flag & (PROJECT_BREATH)) {
105         breath = true;
106         if (flag & PROJECT_HIDE) {
107             old_hide = true;
108         }
109         flag |= PROJECT_HIDE;
110     }
111
112     for (auto dist = 0; dist < 32; dist++) {
113         gm[dist] = 0;
114     }
115
116     if (flag & (PROJECT_BEAM)) {
117         gy[grids] = y1;
118         gx[grids] = x1;
119         grids++;
120     }
121
122     switch (typ) {
123     case AttributeType::LITE:
124     case AttributeType::LITE_WEAK:
125         if (breath || (flag & PROJECT_BEAM)) {
126             flag |= (PROJECT_LOS);
127         }
128         break;
129     case AttributeType::DISINTEGRATE:
130         flag |= (PROJECT_GRID);
131         if (breath || (flag & PROJECT_BEAM)) {
132             flag |= (PROJECT_DISI);
133         }
134         break;
135     default:
136         break;
137     }
138
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);
143
144     int k = 0;
145     auto oy = y1;
146     auto ox = x1;
147     auto visual = false;
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)) {
155                 break;
156             }
157         } else if (flag & PROJECT_LOS) {
158             if (!cave_los_bold(&floor, ny, nx) && (rad > 0)) {
159                 break;
160             }
161         } else {
162             if (!cave_has_flag_bold(&floor, ny, nx, TerrainCharacteristics::PROJECT) && (rad > 0)) {
163                 break;
164             }
165         }
166         if (flag & (PROJECT_BEAM)) {
167             gy[grids] = ny;
168             gx[grids] = nx;
169             grids++;
170         }
171
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);
177                     term_fresh();
178                     term_xtra(TERM_XTRA_DELAY, delay_factor);
179                     lite_spot(player_ptr, ny, nx);
180                     term_fresh();
181                     if (flag & (PROJECT_BEAM)) {
182                         print_bolt_pict(player_ptr, ny, nx, ny, nx, typ);
183                     }
184
185                     visual = true;
186                 } else if (visual) {
187                     term_xtra(TERM_XTRA_DELAY, delay_factor);
188                 }
189             }
190         }
191
192         oy = ny;
193         ox = nx;
194         k++;
195     }
196
197     path_n = k;
198     POSITION by = oy;
199     POSITION bx = ox;
200     if (breath && !path_n) {
201         breath = false;
202         if (!old_hide) {
203             flag &= ~(PROJECT_HIDE);
204         }
205     }
206
207     gm[0] = 0;
208     gm[1] = grids;
209     project_length = 0;
210
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)) {
215             grids--;
216         }
217
218         /*
219          * Create a conical breath attack
220          *
221          *       ***
222          *   ********
223          * D********@**
224          *   ********
225          *       ***
226          */
227         if (breath) {
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);
230         } else {
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)) {
235                             continue;
236                         }
237                         if (distance(by, bx, y, x) != dist) {
238                             continue;
239                         }
240
241                         switch (typ) {
242                         case AttributeType::LITE:
243                         case AttributeType::LITE_WEAK:
244                             if (!los(player_ptr, by, bx, y, x)) {
245                                 continue;
246                             }
247                             break;
248                         case AttributeType::DISINTEGRATE:
249                             if (!in_disintegration_range(player_ptr->current_floor_ptr, by, bx, y, x)) {
250                                 continue;
251                             }
252                             break;
253                         default:
254                             if (!projectable(player_ptr, by, bx, y, x)) {
255                                 continue;
256                             }
257                             break;
258                         }
259
260                         gy[grids] = y;
261                         gx[grids] = x;
262                         grids++;
263                     }
264                 }
265
266                 gm[dist + 1] = grids;
267             }
268         }
269     }
270
271     if (!grids) {
272         return res;
273     }
274
275     if (!is_blind && !(flag & (PROJECT_HIDE)) && (delay_factor > 0)) {
276         auto drawn = false;
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)) {
281                     drawn = true;
282                     print_bolt_pict(player_ptr, pos.y, pos.x, pos.y, pos.x, typ);
283                 }
284             }
285
286             move_cursor_relative(by, bx);
287             term_fresh();
288             if (visual || drawn) {
289                 term_xtra(TERM_XTRA_DELAY, delay_factor);
290             }
291         }
292
293         if (drawn) {
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);
298                 }
299             }
300
301             move_cursor_relative(by, bx);
302             term_fresh();
303         }
304     }
305
306     update_creature(player_ptr);
307
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)));
311     }
312
313     if (flag & (PROJECT_GRID)) {
314         auto dist = 0;
315         for (int i = 0; i < grids; i++) {
316             if (gm[dist + 1] == i) {
317                 dist++;
318             }
319             auto y = gy[i];
320             auto x = gx[i];
321             if (breath) {
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)) {
324                     res.notice = true;
325                 }
326             } else {
327                 if (affect_feature(player_ptr, src_idx, dist, y, x, dam, typ)) {
328                     res.notice = true;
329                 }
330             }
331         }
332     }
333
334     update_creature(player_ptr);
335     if (flag & (PROJECT_ITEM)) {
336         auto dist = 0;
337         for (int i = 0; i < grids; i++) {
338             if (gm[dist + 1] == i) {
339                 dist++;
340             }
341
342             auto y = gy[i];
343             auto x = gx[i];
344             if (breath) {
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)) {
347                     res.notice = true;
348                 }
349             } else {
350                 if (affect_item(player_ptr, src_idx, dist, y, x, dam, typ)) {
351                     res.notice = true;
352                 }
353             }
354         }
355     }
356
357     if (flag & (PROJECT_KILL)) {
358         project_m_n = 0;
359         project_m_x = 0;
360         project_m_y = 0;
361         auto dist = 0;
362         for (int i = 0; i < grids; i++) {
363             int effective_dist;
364             if (gm[dist + 1] == i) {
365                 dist++;
366             }
367
368             const Pos2D pos(gy[i], gx[i]);
369             const auto &grid = floor.get_grid(pos);
370             if (grids <= 1) {
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)) {
374
375                     POSITION t_y, t_x;
376                     int max_attempts = 10;
377                     do {
378                         t_y = y1 - 1 + randint1(3);
379                         t_x = x1 - 1 + randint1(3);
380                         max_attempts--;
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));
382
383                     if (max_attempts < 1) {
384                         t_y = y1;
385                         t_x = x1;
386                     }
387
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!"));
394                         } else {
395                             msg_print(_("攻撃は跳ね返った!", "The attack bounces!"));
396                         }
397                     } else if (!is_monster(src_idx)) {
398                         sound(SOUND_REFLECT);
399                     }
400
401                     if (is_original_ap_and_seen(player_ptr, m_ptr)) {
402                         ref_ptr->r_misc_flags.set(MonsterMiscType::REFLECTING);
403                     }
404
405                     if (player_ptr->is_located_at(pos) || one_in_(2)) {
406                         flag &= ~(PROJECT_PLAYER);
407                     } else {
408                         flag |= PROJECT_PLAYER;
409                     }
410
411                     project(player_ptr, grid.m_idx, 0, t_y, t_x, dam, typ, flag);
412                     continue;
413                 }
414             }
415
416             /* Find the closest point in the blast */
417             if (breath) {
418                 effective_dist = dist_to_line(pos.y, pos.x, y1, x1, by, bx);
419             } else {
420                 effective_dist = dist;
421             }
422
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)) {
426                         /*
427                          * A beam or bolt is well aimed
428                          * at the PLAYER!
429                          * So don't affects the mount.
430                          */
431                         continue;
432                     } else {
433                         /*
434                          * The spell is not well aimed,
435                          * So partly affect the mount too.
436                          */
437                         effective_dist++;
438                     }
439                 }
440
441                 /*
442                  * This grid is the original target.
443                  * Or aimed on your horse.
444                  */
445                 else if (((pos.y == y2) && (pos.x == x2)) || (flag & PROJECT_AIMED)) {
446                     /* Hit the mount with full damage */
447                 }
448
449                 /*
450                  * Otherwise this grid is not the
451                  * original target, it means that line
452                  * of fire is obstructed by this
453                  * monster.
454                  */
455                 /*
456                  * A beam or bolt will hit either
457                  * player or mount.  Choose randomly.
458                  */
459                 else if (flag & (PROJECT_BEAM | PROJECT_REFLECTABLE)) {
460                     if (one_in_(2)) {
461                         /* Hit the mount with full damage */
462                     } else {
463                         flag |= PROJECT_PLAYER;
464                         continue;
465                     }
466                 }
467
468                 /*
469                  * The spell is not well aimed, so
470                  * partly affect both player and
471                  * mount.
472                  */
473                 else {
474                     effective_dist++;
475                 }
476             }
477
478             if (affect_monster(player_ptr, src_idx, effective_dist, pos.y, pos.x, dam, typ, flag, see_s_msg, cap_mon_ptr)) {
479                 res.notice = true;
480             }
481         }
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];
488                 if (monster.ml) {
489                     if (!player_ptr->effects()->hallucination()->is_hallucinated()) {
490                         monster_race_track(player_ptr, monster.ap_r_idx);
491                     }
492
493                     health_track(player_ptr, grid.m_idx);
494                 }
495             }
496         }
497     }
498
499     if (flag & (PROJECT_KILL)) {
500         auto dist = 0;
501         for (int i = 0; i < grids; i++) {
502             int effective_dist;
503             if (gm[dist + 1] == i) {
504                 dist++;
505             }
506
507             const Pos2D pos(gy[i], gx[i]);
508             if (!player_ptr->is_located_at(pos)) {
509                 continue;
510             }
511
512             /* Find the closest point in the blast */
513             if (breath) {
514                 effective_dist = dist_to_line(pos.y, pos.x, y1, x1, by, bx);
515             } else {
516                 effective_dist = dist;
517             }
518
519             if (player_ptr->riding) {
520                 if (flag & PROJECT_PLAYER) {
521                     /* Hit the player with full damage */
522                 }
523
524                 /*
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.
529                  *
530                  * But already choosen to hit the
531                  * mount at this point.
532                  *
533                  * Or aimed on your horse.
534                  */
535                 else if (flag & (PROJECT_BEAM | PROJECT_REFLECTABLE | PROJECT_AIMED)) {
536                     /*
537                      * A beam or bolt is well aimed
538                      * at the mount!
539                      * So don't affects the player.
540                      */
541                     continue;
542                 } else {
543                     /*
544                      * The spell is not well aimed,
545                      * So partly affect the player too.
546                      */
547                     effective_dist++;
548                 }
549             }
550
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);
554             }
555
556             if (affect_player(src_idx, player_ptr, who_name.data(), effective_dist, pos.y, pos.x, dam, typ, flag, project)) {
557                 res.notice = true;
558                 res.affected_player = true;
559             }
560         }
561     }
562
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());
568             }
569         }
570
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());
574             }
575         }
576     }
577
578     return res;
579 }