OSDN Git Service

Merge pull request #3814 from Slimebreath6078/feature/Add_Laffey_II
[hengbandforosx/hengbandosx.git] / src / spell-kind / spells-world.cpp
1 /*
2  * @brief 帰還やテレポート・レベル等、フロアを跨ぐ魔法効果の処理
3  * @author Hourier
4  * @date 2022/10/10
5  */
6
7 #include "spell-kind/spells-world.h"
8 #include "cmd-io/cmd-save.h"
9 #include "core/asking-player.h"
10 #include "dungeon/quest-completion-checker.h"
11 #include "floor/floor-mode-changer.h"
12 #include "floor/floor-town.h"
13 #include "floor/geometry.h"
14 #include "floor/wild.h"
15 #include "game-option/birth-options.h"
16 #include "game-option/play-record-options.h"
17 #include "game-option/special-options.h"
18 #include "io/input-key-acceptor.h"
19 #include "io/write-diary.h"
20 #include "main/sound-definitions-table.h"
21 #include "main/sound-of-music.h"
22 #include "market/building-util.h"
23 #include "monster-floor/monster-remover.h"
24 #include "monster-race/monster-race.h"
25 #include "monster/monster-describer.h"
26 #include "monster/monster-description-types.h"
27 #include "system/angband-system.h"
28 #include "system/dungeon-info.h"
29 #include "system/floor-type-definition.h"
30 #include "system/grid-type-definition.h"
31 #include "system/monster-entity.h"
32 #include "system/monster-race-info.h"
33 #include "system/player-type-definition.h"
34 #include "system/redrawing-flags-updater.h"
35 #include "target/projection-path-calculator.h"
36 #include "target/target-checker.h"
37 #include "target/target-setter.h"
38 #include "target/target-types.h"
39 #include "term/screen-processor.h"
40 #include "term/z-form.h"
41 #include "util/int-char-converter.h"
42 #include "view/display-messages.h"
43 #include "world/world.h"
44 #include <algorithm>
45
46 /*!
47  * @brief プレイヤー及びモンスターをレベルテレポートさせる /
48  * Teleport the player one level up or down (random when legal)
49  * @param player_ptr プレイヤーへの参照ポインタ
50  * @param m_idx テレポートの対象となるモンスターID(0ならばプレイヤー) / If m_idx <= 0, target is player.
51  * @todo cmd-save.h への依存あり。コールバックで何とかしたい
52  */
53 void teleport_level(PlayerType *player_ptr, MONSTER_IDX m_idx)
54 {
55     std::string m_name;
56     auto see_m = true;
57     auto &floor = *player_ptr->current_floor_ptr;
58     if (m_idx <= 0) {
59         m_name = _("あなた", "you");
60     } else {
61         auto *m_ptr = &floor.m_list[m_idx];
62         m_name = monster_desc(player_ptr, m_ptr, 0);
63         see_m = is_seen(player_ptr, m_ptr);
64     }
65
66     if (floor.can_teleport_level(m_idx != 0)) {
67         if (see_m) {
68             msg_print(_("効果がなかった。", "There is no effect."));
69         }
70         return;
71     }
72
73     if ((m_idx <= 0) && player_ptr->anti_tele) {
74         msg_print(_("不思議な力がテレポートを防いだ!", "A mysterious force prevents you from teleporting!"));
75         return;
76     }
77
78     bool go_up;
79     if (randint0(100) < 50) {
80         go_up = true;
81     } else {
82         go_up = false;
83     }
84
85     if ((m_idx <= 0) && w_ptr->wizard) {
86         if (input_check("Force to go up? ")) {
87             go_up = true;
88         } else if (input_check("Force to go down? ")) {
89             go_up = false;
90         }
91     }
92
93     const auto &dungeon = floor.get_dungeon_definition();
94     if ((ironman_downward && (m_idx <= 0)) || (floor.dun_level <= dungeon.mindepth)) {
95 #ifdef JP
96         if (see_m) {
97             msg_format("%s^は床を突き破って沈んでいく。", m_name.data());
98         }
99 #else
100         if (see_m) {
101             msg_format("%s^ sink%s through the floor.", m_name.data(), (m_idx <= 0) ? "" : "s");
102         }
103 #endif
104         if (m_idx <= 0) {
105             if (!floor.is_in_dungeon()) {
106                 floor.set_dungeon_index(ironman_downward ? DUNGEON_ANGBAND : player_ptr->recall_dungeon);
107                 player_ptr->oldpy = player_ptr->y;
108                 player_ptr->oldpx = player_ptr->x;
109             }
110
111             if (record_stair) {
112                 exe_write_diary(player_ptr, DiaryKind::TELEPORT_LEVEL, 1);
113             }
114
115             if (autosave_l) {
116                 do_cmd_save_game(player_ptr, true);
117             }
118
119             if (!floor.is_in_dungeon()) {
120                 const auto &recall_dungeon = floor.get_dungeon_definition();
121                 floor.dun_level = recall_dungeon.mindepth;
122                 prepare_change_floor_mode(player_ptr, CFM_RAND_PLACE);
123             } else {
124                 prepare_change_floor_mode(player_ptr, CFM_SAVE_FLOORS | CFM_DOWN | CFM_RAND_PLACE | CFM_RAND_CONNECT);
125             }
126
127             player_ptr->leaving = true;
128         }
129     } else if (inside_quest(floor.get_quest_id()) || (floor.dun_level >= dungeon.maxdepth)) {
130 #ifdef JP
131         if (see_m) {
132             msg_format("%s^は天井を突き破って宙へ浮いていく。", m_name.data());
133         }
134 #else
135         if (see_m) {
136             msg_format("%s^ rise%s up through the ceiling.", m_name.data(), (m_idx <= 0) ? "" : "s");
137         }
138 #endif
139
140         if (m_idx <= 0) {
141             if (record_stair) {
142                 exe_write_diary(player_ptr, DiaryKind::TELEPORT_LEVEL, -1);
143             }
144
145             if (autosave_l) {
146                 do_cmd_save_game(player_ptr, true);
147             }
148
149             prepare_change_floor_mode(player_ptr, CFM_SAVE_FLOORS | CFM_UP | CFM_RAND_PLACE | CFM_RAND_CONNECT);
150
151             leave_quest_check(player_ptr);
152             floor.quest_number = QuestId::NONE;
153             player_ptr->leaving = true;
154         }
155     } else if (go_up) {
156 #ifdef JP
157         if (see_m) {
158             msg_format("%s^は天井を突き破って宙へ浮いていく。", m_name.data());
159         }
160 #else
161         if (see_m) {
162             msg_format("%s^ rise%s up through the ceiling.", m_name.data(), (m_idx <= 0) ? "" : "s");
163         }
164 #endif
165
166         if (m_idx <= 0) {
167             if (record_stair) {
168                 exe_write_diary(player_ptr, DiaryKind::TELEPORT_LEVEL, -1);
169             }
170
171             if (autosave_l) {
172                 do_cmd_save_game(player_ptr, true);
173             }
174
175             prepare_change_floor_mode(player_ptr, CFM_SAVE_FLOORS | CFM_UP | CFM_RAND_PLACE | CFM_RAND_CONNECT);
176             player_ptr->leaving = true;
177         }
178     } else {
179 #ifdef JP
180         if (see_m) {
181             msg_format("%s^は床を突き破って沈んでいく。", m_name.data());
182         }
183 #else
184         if (see_m) {
185             msg_format("%s^ sink%s through the floor.", m_name.data(), (m_idx <= 0) ? "" : "s");
186         }
187 #endif
188
189         if (m_idx <= 0) {
190             if (record_stair) {
191                 exe_write_diary(player_ptr, DiaryKind::TELEPORT_LEVEL, 1);
192             }
193             if (autosave_l) {
194                 do_cmd_save_game(player_ptr, true);
195             }
196
197             prepare_change_floor_mode(player_ptr, CFM_SAVE_FLOORS | CFM_DOWN | CFM_RAND_PLACE | CFM_RAND_CONNECT);
198             player_ptr->leaving = true;
199         }
200     }
201
202     if (m_idx <= 0) {
203         sound(SOUND_TPLEVEL);
204         return;
205     }
206
207     auto *m_ptr = &floor.m_list[m_idx];
208     QuestCompletionChecker(player_ptr, m_ptr).complete();
209     if (record_named_pet && m_ptr->is_named_pet()) {
210         const auto m2_name = monster_desc(player_ptr, m_ptr, MD_INDEF_VISIBLE);
211         exe_write_diary(player_ptr, DiaryKind::NAMED_PET, RECORD_NAMED_PET_TELE_LEVEL, m2_name);
212     }
213
214     delete_monster_idx(player_ptr, m_idx);
215     if (see_m) {
216         sound(SOUND_TPLEVEL);
217     }
218 }
219
220 bool teleport_level_other(PlayerType *player_ptr)
221 {
222     if (!target_set(player_ptr, TARGET_KILL)) {
223         return false;
224     }
225
226     const auto &floor = *player_ptr->current_floor_ptr;
227     const Pos2D pos(target_row, target_col);
228     const auto &grid = floor.get_grid(pos);
229     const auto target_m_idx = grid.m_idx;
230     if (!target_m_idx) {
231         return true;
232     }
233     if (!grid.has_los()) {
234         return true;
235     }
236     if (!projectable(player_ptr, player_ptr->y, player_ptr->x, target_row, target_col)) {
237         return true;
238     }
239
240     const auto &monster = floor.m_list[target_m_idx];
241     const auto &monrace = monster.get_monrace();
242     const auto m_name = monster_desc(player_ptr, &monster, 0);
243     msg_format(_("%s^の足を指さした。", "You gesture at %s^'s feet."), m_name.data());
244
245     auto has_immune = monrace.resistance_flags.has_any_of(RFR_EFF_RESIST_NEXUS_MASK) || monrace.resistance_flags.has(MonsterResistanceType::RESIST_TELEPORT);
246     if (has_immune || (monrace.misc_flags.has(MonsterMiscType::QUESTOR)) || (monrace.level + randint1(50) > player_ptr->lev + randint1(60))) {
247         msg_print(_("しかし効果がなかった!", format("%s^ is unaffected!", m_name.data())));
248     } else {
249         teleport_level(player_ptr, target_m_idx);
250     }
251
252     return true;
253 }
254
255 /*!
256  * @brief 町間のテレポートを行うメインルーチン
257  * @param player_ptr プレイヤーへの参照ポインタ
258  * @return テレポート処理を決定したか否か
259  */
260 bool tele_town(PlayerType *player_ptr)
261 {
262     if (player_ptr->current_floor_ptr->dun_level) {
263         msg_print(_("この魔法は地上でしか使えない!", "This spell can only be used on the surface!"));
264         return false;
265     }
266
267     if (player_ptr->current_floor_ptr->inside_arena || AngbandSystem::get_instance().is_phase_out()) {
268         msg_print(_("この魔法は外でしか使えない!", "This spell can only be used outside!"));
269         return false;
270     }
271
272     screen_save();
273     clear_bldg(4, 10);
274
275     auto num = 0;
276     const int towns_size = towns_info.size();
277     for (auto i = 1; i < towns_size; i++) {
278         char buf[80];
279
280         if ((i == VALID_TOWNS) || (i == SECRET_TOWN) || (i == player_ptr->town_num) || !(player_ptr->visit & (1UL << (i - 1)))) {
281             continue;
282         }
283
284         strnfmt(buf, sizeof(buf), "%c) %-20s", I2A(i - 1), towns_info[i].name.data());
285         prt(buf, 5 + i, 5);
286         num++;
287     }
288
289     if (num == 0) {
290         msg_print(_("まだ行けるところがない。", "You have not yet visited any town."));
291         msg_print(nullptr);
292         screen_load();
293         return false;
294     }
295
296     prt(_("どこに行きますか:", "Where do you want to go: "), 0, 0);
297     char key;
298     while (true) {
299         key = inkey();
300
301         if (key == ESCAPE) {
302             screen_load();
303             return false;
304         }
305
306         if ((key < 'a') || (key > ('a' + towns_size - 2))) {
307             continue;
308         }
309
310         const auto town_num = key - 'a' + 1;
311         if ((town_num == player_ptr->town_num) || (town_num == VALID_TOWNS) || (town_num == SECRET_TOWN) || !(player_ptr->visit & (1UL << (key - 'a')))) {
312             continue;
313         }
314
315         break;
316     }
317
318     for (POSITION y = 0; y < w_ptr->max_wild_y; y++) {
319         for (POSITION x = 0; x < w_ptr->max_wild_x; x++) {
320             if (wilderness[y][x].town == (key - 'a' + 1)) {
321                 player_ptr->wilderness_y = y;
322                 player_ptr->wilderness_x = x;
323             }
324         }
325     }
326
327     player_ptr->leaving = true;
328     player_ptr->teleport_town = true;
329     screen_load();
330     return true;
331 }
332
333 /*!
334  * @brief 現実変容処理
335  * @param player_ptr プレイヤーへの参照ポインタ
336  */
337 void reserve_alter_reality(PlayerType *player_ptr, TIME_EFFECT turns)
338 {
339     if (player_ptr->current_floor_ptr->inside_arena || ironman_downward) {
340         msg_print(_("何も起こらなかった。", "Nothing happens."));
341         return;
342     }
343
344     auto &rfu = RedrawingFlagsUpdater::get_instance();
345     if (player_ptr->alter_reality || turns == 0) {
346         player_ptr->alter_reality = 0;
347         msg_print(_("景色が元に戻った...", "The view around you returns to normal..."));
348         rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
349         return;
350     }
351
352     player_ptr->alter_reality = turns;
353     msg_print(_("回りの景色が変わり始めた...", "The view around you begins to change..."));
354     rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
355 }
356
357 /*!
358  * @brief これまでに入ったダンジョンの一覧を表示し、選択させる。
359  * @param note ダンジョンに施す処理記述
360  * @param y コンソールY座標
361  * @param x コンソールX座標
362  * @return 選択されたダンジョンID
363  */
364 static DUNGEON_IDX choose_dungeon(concptr note, POSITION y, POSITION x)
365 {
366     DUNGEON_IDX select_dungeon;
367     if (lite_town || vanilla_town || ironman_downward) {
368         if (max_dlv[DUNGEON_ANGBAND]) {
369             return DUNGEON_ANGBAND;
370         } else {
371             msg_format(_("まだ%sに入ったことはない。", "You haven't entered %s yet."), dungeons_info[DUNGEON_ANGBAND].name.data());
372             msg_print(nullptr);
373             return 0;
374         }
375     }
376
377     std::vector<DUNGEON_IDX> dun;
378
379     screen_save();
380     for (const auto &d_ref : dungeons_info) {
381         char buf[80];
382         bool seiha = false;
383
384         if (d_ref.idx == 0 || !d_ref.maxdepth) {
385             continue;
386         }
387         if (!max_dlv[d_ref.idx]) {
388             continue;
389         }
390         if (MonsterRace(d_ref.final_guardian).is_valid()) {
391             if (!monraces_info[d_ref.final_guardian].max_num) {
392                 seiha = true;
393             }
394         } else if (max_dlv[d_ref.idx] == d_ref.maxdepth) {
395             seiha = true;
396         }
397
398         constexpr auto fmt = _("      %c) %c%-12s : 最大 %d 階", "      %c) %c%-16s : Max level %d");
399         strnfmt(buf, sizeof(buf), fmt, static_cast<char>('a' + dun.size()), seiha ? '!' : ' ', d_ref.name.data(), (int)max_dlv[d_ref.idx]);
400         prt(buf, y + dun.size(), x);
401         dun.push_back(d_ref.idx);
402     }
403
404     if (dun.empty()) {
405         prt(_("      選べるダンジョンがない。", "      No dungeon is available."), y, x);
406     }
407
408     prt(format(_("どのダンジョン%sしますか:", "Which dungeon do you %s?: "), note), 0, 0);
409     while (true) {
410         auto i = inkey();
411         if ((i == ESCAPE) || dun.empty()) {
412             screen_load();
413             return 0;
414         }
415         if (i >= 'a' && i < static_cast<char>('a' + dun.size())) {
416             select_dungeon = dun[i - 'a'];
417             break;
418         } else {
419             bell();
420         }
421     }
422     screen_load();
423
424     return select_dungeon;
425 }
426
427 /*!
428  * @brief プレイヤーの帰還発動及び中止処理 /
429  * Recall the player to town or dungeon
430  * @param player_ptr プレイヤーへの参照ポインタ
431  * @param turns 発動までのターン数
432  * @return 常にTRUEを返す
433  * @todo Recall the player to the last visited town when in the wilderness
434  */
435 bool recall_player(PlayerType *player_ptr, TIME_EFFECT turns)
436 {
437     const auto &floor = *player_ptr->current_floor_ptr;
438     if (floor.inside_arena || ironman_downward) {
439         msg_print(_("何も起こらなかった。", "Nothing happens."));
440         return true;
441     }
442
443     bool is_special_floor = floor.is_in_dungeon();
444     is_special_floor &= max_dlv[floor.dungeon_idx] > floor.dun_level;
445     is_special_floor &= !floor.is_in_quest();
446     is_special_floor &= !player_ptr->word_recall;
447     if (is_special_floor) {
448         if (input_check(_("ここは最深到達階より浅い階です。この階に戻って来ますか? ", "Reset recall depth? "))) {
449             max_dlv[floor.dungeon_idx] = floor.dun_level;
450             if (record_maxdepth) {
451                 exe_write_diary(player_ptr, DiaryKind::TRUMP, floor.dungeon_idx, _("帰還のときに", "when recalled from dungeon"));
452             }
453         }
454     }
455
456     auto &rfu = RedrawingFlagsUpdater::get_instance();
457     if (player_ptr->word_recall || turns == 0) {
458         player_ptr->word_recall = 0;
459         msg_print(_("張りつめた大気が流れ去った...", "A tension leaves the air around you..."));
460         rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
461         return true;
462     }
463
464     if (!floor.is_in_dungeon()) {
465         DUNGEON_IDX select_dungeon;
466         select_dungeon = choose_dungeon(_("に帰還", "recall"), 2, 14);
467         if (!select_dungeon) {
468             return false;
469         }
470         player_ptr->recall_dungeon = select_dungeon;
471     }
472
473     player_ptr->word_recall = turns;
474     msg_print(_("回りの大気が張りつめてきた...", "The air about you becomes charged..."));
475     rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
476     return true;
477 }
478
479 bool free_level_recall(PlayerType *player_ptr)
480 {
481     DUNGEON_IDX select_dungeon = choose_dungeon(_("にテレポート", "teleport"), 4, 0);
482     if (!select_dungeon) {
483         return false;
484     }
485
486     const auto &dungeon = dungeons_info[select_dungeon];
487     auto max_depth = dungeon.maxdepth;
488     if (select_dungeon == DUNGEON_ANGBAND) {
489         const auto &quest_list = QuestList::get_instance();
490         if (quest_list[QuestId::OBERON].status != QuestStatusType::FINISHED) {
491             max_depth = 98;
492         } else if (quest_list[QuestId::SERPENT].status != QuestStatusType::FINISHED) {
493             max_depth = 99;
494         }
495     }
496
497     const auto mes = _("%sの何階にテレポートしますか?", "Teleport to which level of %s? ");
498     const auto amt = input_quantity(max_depth, format(mes, dungeon.name.data()));
499     if (amt <= 0) {
500         return false;
501     }
502
503     player_ptr->word_recall = 1;
504     player_ptr->recall_dungeon = select_dungeon;
505     max_dlv[player_ptr->recall_dungeon] = ((amt > dungeon.maxdepth) ? dungeon.maxdepth
506                                                                     : ((amt < dungeon.mindepth) ? dungeon.mindepth : amt));
507     if (record_maxdepth) {
508         exe_write_diary(player_ptr, DiaryKind::TRUMP, select_dungeon, _("トランプタワーで", "at Trump Tower"));
509     }
510
511     msg_print(_("回りの大気が張りつめてきた...", "The air about you becomes charged..."));
512     RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
513     return true;
514 }
515
516 /*!
517  * @brief フロア・リセット処理
518  * @param player_ptr プレイヤーへの参照ポインタ
519  * @return リセット処理が実際に行われたらTRUEを返す
520  */
521 bool reset_recall(PlayerType *player_ptr)
522 {
523     auto select_dungeon = choose_dungeon(_("をセット", "reset"), 2, 14);
524     if (ironman_downward) {
525         msg_print(_("何も起こらなかった。", "Nothing happens."));
526         return true;
527     }
528
529     if (!select_dungeon) {
530         return false;
531     }
532
533     constexpr auto prompt = _("何階にセットしますか?", "Reset to which level?");
534     const auto min_level = dungeons_info[select_dungeon].mindepth;
535     const auto max_level = max_dlv[select_dungeon];
536     const auto reset_level = input_numerics(prompt, min_level, max_level, player_ptr->current_floor_ptr->dun_level);
537     if (!reset_level) {
538         return false;
539     }
540
541     max_dlv[select_dungeon] = *reset_level;
542     if (record_maxdepth) {
543         constexpr auto note = _("フロア・リセットで", "using a scroll of reset recall");
544         exe_write_diary(player_ptr, DiaryKind::TRUMP, select_dungeon, note);
545     }
546 #ifdef JP
547     msg_format("%sの帰還レベルを %d 階にセット。", dungeons_info[select_dungeon].name.data(), *reset_level);
548 #else
549     msg_format("Recall depth set to level %d (%d').", *reset_level, *reset_level * 50);
550 #endif
551     return true;
552 }