OSDN Git Service

[Refactor] #3286 Removed player-redraw-types.h
[hengbandforosx/hengbandosx.git] / src / target / target-setter.cpp
1 #include "target/target-setter.h"
2 #include "core/stuff-handler.h"
3 #include "core/window-redrawer.h"
4 #include "floor/geometry.h"
5 #include "floor/line-of-sight.h"
6 #include "game-option/cheat-options.h"
7 #include "game-option/game-play-options.h"
8 #include "game-option/input-options.h"
9 #include "io/cursor.h"
10 #include "io/input-key-requester.h"
11 #include "io/screen-util.h"
12 #include "main/sound-of-music.h"
13 #include "system/floor-type-definition.h"
14 #include "system/grid-type-definition.h"
15 #include "system/player-type-definition.h"
16 #include "system/redrawing-flags-updater.h"
17 #include "target/projection-path-calculator.h"
18 #include "target/target-checker.h"
19 #include "target/target-describer.h"
20 #include "target/target-preparation.h"
21 #include "target/target-types.h"
22 #include "term/screen-processor.h"
23 #include "term/z-form.h"
24 #include "util/bit-flags-calculator.h"
25 #include "util/int-char-converter.h"
26 #include "util/string-processor.h"
27 #include "window/display-sub-windows.h"
28 #include "window/main-window-util.h"
29 #include <vector>
30
31 // "interesting" な座標たちを記録する配列。
32 // ang_sort() を利用する関係上、y/x座標それぞれについて配列を作る。
33 static std::vector<POSITION> ys_interest;
34 static std::vector<POSITION> xs_interest;
35
36 // Target Setter.
37 struct ts_type {
38     target_type mode;
39     POSITION y;
40     POSITION x;
41     POSITION y2; // panel_row_min 退避用
42     POSITION x2; // panel_col_min 退避用
43     bool done;
44     bool flag; // 移動コマンド入力時、"interesting" な座標へ飛ぶかどうか
45     char query;
46     char info[80];
47     grid_type *g_ptr;
48     TERM_LEN wid, hgt;
49     int m; // "interesting" な座標たちのうち現在ターゲットしているもののインデックス
50     int distance; // カーソルの移動方向 (1,2,3,4,6,7,8,9)
51     int target_num; // target_pick() の結果
52     bool move_fast; // カーソル移動を粗くする(1マスずつ移動しない)
53 };
54
55 static ts_type *initialize_target_set_type(PlayerType *player_ptr, ts_type *ts_ptr, target_type mode)
56 {
57     ts_ptr->mode = mode;
58     ts_ptr->y = player_ptr->y;
59     ts_ptr->x = player_ptr->x;
60     ts_ptr->done = false;
61     ts_ptr->flag = true;
62     get_screen_size(&ts_ptr->wid, &ts_ptr->hgt);
63     ts_ptr->m = 0;
64     return ts_ptr;
65 }
66
67 /*!
68  * @brief フォーカスを当てるべきマップ描画の基準座標を指定する
69  * @param player_ptr プレイヤーへの参照ポインタ
70  * @param y 変更先のフロアY座標
71  * @param x 変更先のフロアX座標
72  * @details
73  * Handle a request to change the current panel
74  * Return TRUE if the panel was changed.
75  * Also used in do_cmd_locate
76  * @return 実際に再描画が必要だった場合TRUEを返す
77  */
78 static bool change_panel_xy(PlayerType *player_ptr, POSITION y, POSITION x)
79 {
80     POSITION dy = 0, dx = 0;
81     TERM_LEN wid, hgt;
82     get_screen_size(&wid, &hgt);
83     if (y < panel_row_min) {
84         dy = -1;
85     }
86
87     if (y > panel_row_max) {
88         dy = 1;
89     }
90
91     if (x < panel_col_min) {
92         dx = -1;
93     }
94
95     if (x > panel_col_max) {
96         dx = 1;
97     }
98
99     if (!dy && !dx) {
100         return false;
101     }
102
103     return change_panel(player_ptr, dy, dx);
104 }
105
106 /*!
107  * @brief "interesting" な座標たちのうち、(y1,x1) から (dy,dx) 方向にある最も近いもののインデックスを得る。
108  * @param y1 現在地座標y
109  * @param x1 現在地座標x
110  * @param dy 現在地からの向きy [-1,1]
111  * @param dx 現在地からの向きx [-1,1]
112  * @return 最も近い座標のインデックス。適切なものがない場合 -1
113  */
114 static POSITION_IDX target_pick(const POSITION y1, const POSITION x1, const POSITION dy, const POSITION dx)
115 {
116     // 最も近いもののインデックスとその距離。
117     POSITION_IDX b_i = -1, b_v = 9999;
118
119     for (POSITION_IDX i = 0; i < (POSITION_IDX)size(ys_interest); i++) {
120         const POSITION x2 = xs_interest[i];
121         const POSITION y2 = ys_interest[i];
122
123         // (y1,x1) から (y2,x2) へ向かうベクトル。
124         const POSITION x3 = (x2 - x1);
125         const POSITION y3 = (y2 - y1);
126
127         // (dy,dx) 方向にないものを除外する。
128
129         // dx > 0 のとき、x3 <= 0 なるものは除外。
130         // dx < 0 のとき、x3 >= 0 なるものは除外。
131         if (dx && (x3 * dx <= 0)) {
132             continue;
133         }
134
135         // dy > 0 のとき、y3 <= 0 なるものは除外。
136         // dy < 0 のとき、y3 >= 0 なるものは除外。
137         if (dy && (y3 * dy <= 0)) {
138             continue;
139         }
140
141         const POSITION x4 = std::abs(x3);
142         const POSITION y4 = std::abs(y3);
143
144         // (dy,dx) が (-1,0) or (1,0) のとき、|x3| > |y3| なるものは除外。
145         if (dy && !dx && (x4 > y4)) {
146             continue;
147         }
148
149         // (dy,dx) が (0,-1) or (0,1) のとき、|y3| > |x3| なるものは除外。
150         if (dx && !dy && (y4 > x4)) {
151             continue;
152         }
153
154         // (y1,x1), (y2,x2) 間の距離を求め、最も近いものを更新する。
155         // 距離の定義は v の式を参照。
156         const POSITION_IDX v = ((x4 > y4) ? (x4 + x4 + y4) : (y4 + y4 + x4));
157         if ((b_i >= 0) && (v >= b_v)) {
158             continue;
159         }
160
161         b_i = i;
162         b_v = v;
163     }
164
165     return b_i;
166 }
167
168 static void describe_projectablity(PlayerType *player_ptr, ts_type *ts_ptr)
169 {
170     ts_ptr->y = ys_interest[ts_ptr->m];
171     ts_ptr->x = xs_interest[ts_ptr->m];
172     change_panel_xy(player_ptr, ts_ptr->y, ts_ptr->x);
173     if ((ts_ptr->mode & TARGET_LOOK) == 0) {
174         print_path(player_ptr, ts_ptr->y, ts_ptr->x);
175     }
176
177     ts_ptr->g_ptr = &player_ptr->current_floor_ptr->grid_array[ts_ptr->y][ts_ptr->x];
178     if (target_able(player_ptr, ts_ptr->g_ptr->m_idx)) {
179         angband_strcpy(ts_ptr->info, _("q止 t決 p自 o現 +次 -前", "q,t,p,o,+,-,<dir>"), sizeof(ts_ptr->info));
180     } else {
181         angband_strcpy(ts_ptr->info, _("q止 p自 o現 +次 -前", "q,p,o,+,-,<dir>"), sizeof(ts_ptr->info));
182     }
183
184     if (!cheat_sight) {
185         return;
186     }
187
188     char cheatinfo[30];
189     strnfmt(cheatinfo, sizeof(cheatinfo), " X:%d Y:%d LOS:%d LOP:%d", ts_ptr->x,
190         ts_ptr->y,
191         los(player_ptr, player_ptr->y, player_ptr->x, ts_ptr->y, ts_ptr->x),
192         projectable(player_ptr, player_ptr->y, player_ptr->x, ts_ptr->y, ts_ptr->x));
193     angband_strcat(ts_ptr->info, cheatinfo, sizeof(ts_ptr->info));
194 }
195
196 static void menu_target(ts_type *ts_ptr)
197 {
198     if (!use_menu) {
199         return;
200     }
201
202     if (ts_ptr->query == '\r') {
203         ts_ptr->query = 't';
204     }
205 }
206
207 static void switch_target_input(PlayerType *player_ptr, ts_type *ts_ptr)
208 {
209     ts_ptr->distance = 0;
210     switch (ts_ptr->query) {
211     case ESCAPE:
212     case 'q':
213         ts_ptr->done = true;
214         return;
215     case 't':
216     case '.':
217     case '5':
218     case '0':
219         if (!target_able(player_ptr, ts_ptr->g_ptr->m_idx)) {
220             bell();
221             return;
222         }
223
224         health_track(player_ptr, ts_ptr->g_ptr->m_idx);
225         target_who = ts_ptr->g_ptr->m_idx;
226         target_row = ts_ptr->y;
227         target_col = ts_ptr->x;
228         ts_ptr->done = true;
229         return;
230     case ' ':
231     case '*':
232     case '+':
233         if (++ts_ptr->m != (int)size(ys_interest)) {
234             return;
235         }
236
237         ts_ptr->m = 0;
238         if (!expand_list) {
239             ts_ptr->done = true;
240         }
241
242         return;
243     case '-':
244         if (ts_ptr->m-- != 0) {
245             return;
246         }
247
248         ts_ptr->m = (int)size(ys_interest) - 1;
249         if (!expand_list) {
250             ts_ptr->done = true;
251         }
252
253         return;
254     case 'p': {
255         verify_panel(player_ptr);
256         auto &rfu = RedrawingFlagsUpdater::get_instance();
257         rfu.set_flag(StatusRedrawingFlag::MONSTER_STATUSES);
258         rfu.set_flag(MainWindowRedrawingFlag::MAP);
259         player_ptr->window_flags |= PW_OVERHEAD;
260         handle_stuff(player_ptr);
261         target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
262         ts_ptr->y = player_ptr->y;
263         ts_ptr->x = player_ptr->x;
264     }
265         [[fallthrough]];
266     case 'o':
267         ts_ptr->flag = false;
268         return;
269     case 'm':
270         return;
271     default: {
272         const char queried_command = rogue_like_commands ? 'x' : 'l';
273         if (ts_ptr->query != queried_command) {
274             ts_ptr->distance = get_keymap_dir(ts_ptr->query);
275             if (ts_ptr->distance == 0) {
276                 bell();
277             }
278
279             return;
280         }
281
282         if (++ts_ptr->m != (int)size(ys_interest)) {
283             return;
284         }
285
286         ts_ptr->m = 0;
287         if (!expand_list) {
288             ts_ptr->done = true;
289         }
290
291         return;
292     }
293     }
294 }
295
296 /*!
297  * @brief カーソル移動に伴い、描画範囲、"interesting" 座標リスト、現在のターゲットを更新する。
298  * @return カーソル移動によって描画範囲が変化したかどうか
299  */
300 static bool check_panel_changed(PlayerType *player_ptr, ts_type *ts_ptr)
301 {
302     // カーソル移動によって描画範囲が変化しないなら何もせずその旨を返す。
303     if (!change_panel(player_ptr, ddy[ts_ptr->distance], ddx[ts_ptr->distance])) {
304         return false;
305     }
306
307     // 描画範囲が変化した場合、"interesting" 座標リストおよび現在のターゲットを更新する必要がある。
308
309     // "interesting" 座標を探す起点。
310     // ts_ptr->m が有効な座標を指していればそれを使う。
311     // さもなくば (ts_ptr->y, ts_ptr->x) を使う。
312     int v, u;
313     if (ts_ptr->m < (int)size(ys_interest)) {
314         v = ys_interest[ts_ptr->m];
315         u = xs_interest[ts_ptr->m];
316     } else {
317         v = ts_ptr->y;
318         u = ts_ptr->x;
319     }
320
321     // 新たな描画範囲を用いて "interesting" 座標リストを更新。
322     target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
323
324     // 新たな "interesting" 座標リストからターゲットを探す。
325     ts_ptr->flag = true;
326     ts_ptr->target_num = target_pick(v, u, ddy[ts_ptr->distance], ddx[ts_ptr->distance]);
327     if (ts_ptr->target_num >= 0) {
328         ts_ptr->m = ts_ptr->target_num;
329     }
330
331     return true;
332 }
333
334 /*!
335  * @brief カーソル移動方向に "interesting" な座標がなかったとき、画面外まで探す。
336  *
337  * 既に "interesting" な座標を発見している場合、この関数は何もしない。
338  */
339 static void sweep_targets(PlayerType *player_ptr, ts_type *ts_ptr)
340 {
341     auto *floor_ptr = player_ptr->current_floor_ptr;
342     auto &rfu = RedrawingFlagsUpdater::get_instance();
343     while (ts_ptr->flag && (ts_ptr->target_num < 0)) {
344         // カーソル移動に伴い、必要なだけ描画範囲を更新。
345         // "interesting" 座標リストおよび現在のターゲットも更新。
346         if (check_panel_changed(player_ptr, ts_ptr)) {
347             continue;
348         }
349
350         POSITION dx = ddx[ts_ptr->distance];
351         POSITION dy = ddy[ts_ptr->distance];
352         panel_row_min = ts_ptr->y2;
353         panel_col_min = ts_ptr->x2;
354         panel_bounds_center();
355         rfu.set_flag(StatusRedrawingFlag::MONSTER_STATUSES);
356         rfu.set_flag(MainWindowRedrawingFlag::MAP);
357         player_ptr->window_flags |= PW_OVERHEAD;
358         handle_stuff(player_ptr);
359         target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
360         ts_ptr->flag = false;
361         ts_ptr->x += dx;
362         ts_ptr->y += dy;
363         if (((ts_ptr->x < panel_col_min + ts_ptr->wid / 2) && (dx > 0)) || ((ts_ptr->x > panel_col_min + ts_ptr->wid / 2) && (dx < 0))) {
364             dx = 0;
365         }
366
367         if (((ts_ptr->y < panel_row_min + ts_ptr->hgt / 2) && (dy > 0)) || ((ts_ptr->y > panel_row_min + ts_ptr->hgt / 2) && (dy < 0))) {
368             dy = 0;
369         }
370
371         if ((ts_ptr->y >= panel_row_min + ts_ptr->hgt) || (ts_ptr->y < panel_row_min) || (ts_ptr->x >= panel_col_min + ts_ptr->wid) || (ts_ptr->x < panel_col_min)) {
372             if (change_panel(player_ptr, dy, dx)) {
373                 target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
374             }
375         }
376
377         if (ts_ptr->x >= floor_ptr->width - 1) {
378             ts_ptr->x = floor_ptr->width - 2;
379         } else if (ts_ptr->x <= 0) {
380             ts_ptr->x = 1;
381         }
382
383         if (ts_ptr->y >= floor_ptr->height - 1) {
384             ts_ptr->y = floor_ptr->height - 2;
385         } else if (ts_ptr->y <= 0) {
386             ts_ptr->y = 1;
387         }
388     }
389 }
390
391 static bool set_target_grid(PlayerType *player_ptr, ts_type *ts_ptr)
392 {
393     if (!ts_ptr->flag || ys_interest.empty()) {
394         return false;
395     }
396
397     describe_projectablity(player_ptr, ts_ptr);
398     fix_floor_item_list(player_ptr, ts_ptr->y, ts_ptr->x);
399
400     while (true) {
401         ts_ptr->query = examine_grid(player_ptr, ts_ptr->y, ts_ptr->x, ts_ptr->mode, ts_ptr->info);
402         if (ts_ptr->query) {
403             break;
404         }
405     }
406
407     menu_target(ts_ptr);
408     switch_target_input(player_ptr, ts_ptr);
409     if (ts_ptr->distance == 0) {
410         return true;
411     }
412
413     ts_ptr->y2 = panel_row_min;
414     ts_ptr->x2 = panel_col_min;
415     {
416         const POSITION y = ys_interest[ts_ptr->m];
417         const POSITION x = xs_interest[ts_ptr->m];
418         ts_ptr->target_num = target_pick(y, x, ddy[ts_ptr->distance], ddx[ts_ptr->distance]);
419     }
420     sweep_targets(player_ptr, ts_ptr);
421     ts_ptr->m = ts_ptr->target_num;
422     return true;
423 }
424
425 static void describe_grid_wizard(PlayerType *player_ptr, ts_type *ts_ptr)
426 {
427     if (!cheat_sight) {
428         return;
429     }
430
431     constexpr auto fmt = " X:%d Y:%d LOS:%d LOP:%d SPECIAL:%d";
432     char cheatinfo[100];
433     const auto is_los = los(player_ptr, player_ptr->y, player_ptr->x, ts_ptr->y, ts_ptr->x);
434     const auto is_projectable = projectable(player_ptr, player_ptr->y, player_ptr->x, ts_ptr->y, ts_ptr->x);
435     strnfmt(cheatinfo, sizeof(cheatinfo), fmt, ts_ptr->x, ts_ptr->y, is_los, is_projectable, ts_ptr->g_ptr->special);
436     angband_strcat(ts_ptr->info, cheatinfo, sizeof(ts_ptr->info));
437 }
438
439 static void switch_next_grid_command(PlayerType *player_ptr, ts_type *ts_ptr)
440 {
441     switch (ts_ptr->query) {
442     case ESCAPE:
443     case 'q':
444         ts_ptr->done = true;
445         break;
446     case 't':
447     case '.':
448     case '5':
449     case '0':
450         target_who = -1;
451         target_row = ts_ptr->y;
452         target_col = ts_ptr->x;
453         ts_ptr->done = true;
454         break;
455     case 'p': {
456         verify_panel(player_ptr);
457         auto &rfu = RedrawingFlagsUpdater::get_instance();
458         rfu.set_flag(StatusRedrawingFlag::MONSTER_STATUSES);
459         rfu.set_flag(MainWindowRedrawingFlag::MAP);
460         player_ptr->window_flags |= PW_OVERHEAD;
461         handle_stuff(player_ptr);
462         target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
463         ts_ptr->y = player_ptr->y;
464         ts_ptr->x = player_ptr->x;
465         break;
466     }
467     case 'o':
468         // ターゲット時の「m近」「o現」の切り替え
469         // すでに「o現」の時にoを押してもなにも起きない
470         break;
471     case ' ':
472     case '*':
473     case '+':
474     case '-':
475     case 'm': {
476         ts_ptr->flag = true;
477         ts_ptr->m = 0;
478         int bd = 999;
479         for (size_t i = 0; i < size(ys_interest); i++) {
480             const POSITION y = ys_interest[i];
481             const POSITION x = xs_interest[i];
482             int t = distance(ts_ptr->y, ts_ptr->x, y, x);
483             if (t < bd) {
484                 ts_ptr->m = i;
485                 bd = t;
486             }
487         }
488
489         if (bd == 999) {
490             ts_ptr->flag = false;
491         }
492
493         break;
494     }
495     default:
496         ts_ptr->distance = get_keymap_dir(ts_ptr->query);
497         if (isupper(ts_ptr->query)) {
498             ts_ptr->move_fast = true;
499         }
500
501         if (!ts_ptr->distance) {
502             bell();
503         }
504
505         break;
506     }
507 }
508
509 static void decide_change_panel(PlayerType *player_ptr, ts_type *ts_ptr)
510 {
511     if (ts_ptr->distance == 0) {
512         return;
513     }
514
515     POSITION dx = ddx[ts_ptr->distance];
516     POSITION dy = ddy[ts_ptr->distance];
517     if (ts_ptr->move_fast) {
518         int mag = std::min(ts_ptr->wid / 2, ts_ptr->hgt / 2);
519         ts_ptr->x += dx * mag;
520         ts_ptr->y += dy * mag;
521     } else {
522         ts_ptr->x += dx;
523         ts_ptr->y += dy;
524     }
525
526     if (((ts_ptr->x < panel_col_min + ts_ptr->wid / 2) && (dx > 0)) || ((ts_ptr->x > panel_col_min + ts_ptr->wid / 2) && (dx < 0))) {
527         dx = 0;
528     }
529
530     if (((ts_ptr->y < panel_row_min + ts_ptr->hgt / 2) && (dy > 0)) || ((ts_ptr->y > panel_row_min + ts_ptr->hgt / 2) && (dy < 0))) {
531         dy = 0;
532     }
533
534     auto should_change_panel = ts_ptr->y >= panel_row_min + ts_ptr->hgt;
535     should_change_panel |= ts_ptr->y < panel_row_min;
536     should_change_panel |= ts_ptr->x >= panel_col_min + ts_ptr->wid;
537     should_change_panel |= ts_ptr->x < panel_col_min;
538     if (should_change_panel && change_panel(player_ptr, dy, dx)) {
539         target_set_prepare(player_ptr, ys_interest, xs_interest, ts_ptr->mode);
540     }
541
542     auto *floor_ptr = player_ptr->current_floor_ptr;
543     if (ts_ptr->x >= floor_ptr->width - 1) {
544         ts_ptr->x = floor_ptr->width - 2;
545     } else if (ts_ptr->x <= 0) {
546         ts_ptr->x = 1;
547     }
548
549     if (ts_ptr->y >= floor_ptr->height - 1) {
550         ts_ptr->y = floor_ptr->height - 2;
551     } else if (ts_ptr->y <= 0) {
552         ts_ptr->y = 1;
553     }
554 }
555
556 static void sweep_target_grids(PlayerType *player_ptr, ts_type *ts_ptr)
557 {
558     while (!ts_ptr->done) {
559         if (set_target_grid(player_ptr, ts_ptr)) {
560             continue;
561         }
562
563         ts_ptr->move_fast = false;
564         if ((ts_ptr->mode & TARGET_LOOK) == 0) {
565             print_path(player_ptr, ts_ptr->y, ts_ptr->x);
566         }
567
568         ts_ptr->g_ptr = &player_ptr->current_floor_ptr->grid_array[ts_ptr->y][ts_ptr->x];
569         strcpy(ts_ptr->info, _("q止 t決 p自 m近 +次 -前", "q,t,p,m,+,-,<dir>"));
570         describe_grid_wizard(player_ptr, ts_ptr);
571         fix_floor_item_list(player_ptr, ts_ptr->y, ts_ptr->x);
572
573         /* Describe and Prompt (enable "TARGET_LOOK") */
574         const auto target = i2enum<target_type>(ts_ptr->mode | TARGET_LOOK);
575         while ((ts_ptr->query = examine_grid(player_ptr, ts_ptr->y, ts_ptr->x, target, ts_ptr->info)) == 0) {
576             ;
577         }
578
579         ts_ptr->distance = 0;
580         if (use_menu && (ts_ptr->query == '\r')) {
581             ts_ptr->query = 't';
582         }
583
584         switch_next_grid_command(player_ptr, ts_ptr);
585         decide_change_panel(player_ptr, ts_ptr);
586     }
587 }
588
589 /*
590  * Handle "target" and "look".
591  */
592 bool target_set(PlayerType *player_ptr, target_type mode)
593 {
594     ts_type tmp_ts;
595     ts_type *ts_ptr = initialize_target_set_type(player_ptr, &tmp_ts, mode);
596     target_who = 0;
597     target_set_prepare(player_ptr, ys_interest, xs_interest, mode);
598     sweep_target_grids(player_ptr, ts_ptr);
599     prt("", 0, 0);
600     verify_panel(player_ptr);
601     auto &rfu = RedrawingFlagsUpdater::get_instance();
602     rfu.set_flag(StatusRedrawingFlag::MONSTER_STATUSES);
603     rfu.set_flag(MainWindowRedrawingFlag::MAP);
604     set_bits(player_ptr->window_flags, PW_OVERHEAD | PW_FLOOR_ITEMS);
605     handle_stuff(player_ptr);
606     return target_who != 0;
607 }