OSDN Git Service

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