OSDN Git Service

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