2 * @brief 帰還やテレポート・レベル等、フロアを跨ぐ魔法効果の処理
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/monster-describer.h"
25 #include "monster/monster-description-types.h"
26 #include "system/angband-system.h"
27 #include "system/dungeon-info.h"
28 #include "system/floor-type-definition.h"
29 #include "system/grid-type-definition.h"
30 #include "system/monster-entity.h"
31 #include "system/monster-race-info.h"
32 #include "system/player-type-definition.h"
33 #include "system/redrawing-flags-updater.h"
34 #include "target/projection-path-calculator.h"
35 #include "target/target-checker.h"
36 #include "target/target-setter.h"
37 #include "target/target-types.h"
38 #include "term/screen-processor.h"
39 #include "term/z-form.h"
40 #include "util/int-char-converter.h"
41 #include "view/display-messages.h"
42 #include "world/world.h"
46 * @brief プレイヤー及びモンスターをレベルテレポートさせる /
47 * Teleport the player one level up or down (random when legal)
48 * @param player_ptr プレイヤーへの参照ポインタ
49 * @param m_idx テレポートの対象となるモンスターID(0ならばプレイヤー) / If m_idx <= 0, target is player.
50 * @todo cmd-save.h への依存あり。コールバックで何とかしたい
52 void teleport_level(PlayerType *player_ptr, MONSTER_IDX m_idx)
56 auto &floor = *player_ptr->current_floor_ptr;
58 m_name = _("あなた", "you");
60 auto *m_ptr = &floor.m_list[m_idx];
61 m_name = monster_desc(player_ptr, m_ptr, 0);
62 see_m = is_seen(player_ptr, m_ptr);
65 if (floor.can_teleport_level(m_idx != 0)) {
67 msg_print(_("効果がなかった。", "There is no effect."));
72 if ((m_idx <= 0) && player_ptr->anti_tele) {
73 msg_print(_("不思議な力がテレポートを防いだ!", "A mysterious force prevents you from teleporting!"));
84 if ((m_idx <= 0) && AngbandWorld::get_instance().wizard) {
85 if (input_check("Force to go up? ")) {
87 } else if (input_check("Force to go down? ")) {
92 const auto &dungeon = floor.get_dungeon_definition();
93 auto &fcms = FloorChangeModesStore::get_instace();
94 if ((ironman_downward && (m_idx <= 0)) || (floor.dun_level <= dungeon.mindepth)) {
97 msg_format("%s^は床を突き破って沈んでいく。", m_name.data());
101 msg_format("%s^ sink%s through the floor.", m_name.data(), (m_idx <= 0) ? "" : "s");
105 if (!floor.is_in_underground()) {
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;
112 exe_write_diary(floor, DiaryKind::TELEPORT_LEVEL, 1);
116 do_cmd_save_game(player_ptr, true);
119 fcms->set(FloorChangeMode::RANDOM_PLACE);
120 if (!floor.is_in_underground()) {
121 const auto &recall_dungeon = floor.get_dungeon_definition();
122 floor.dun_level = recall_dungeon.mindepth;
124 fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::DOWN, FloorChangeMode::RANDOM_CONNECT });
127 player_ptr->leaving = true;
129 } else if (inside_quest(floor.get_quest_id()) || (floor.dun_level >= dungeon.maxdepth)) {
132 msg_format("%s^は天井を突き破って宙へ浮いていく。", m_name.data());
136 msg_format("%s^ rise%s up through the ceiling.", m_name.data(), (m_idx <= 0) ? "" : "s");
142 exe_write_diary(floor, DiaryKind::TELEPORT_LEVEL, -1);
146 do_cmd_save_game(player_ptr, true);
149 fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::UP, FloorChangeMode::RANDOM_PLACE, FloorChangeMode::RANDOM_CONNECT });
150 leave_quest_check(player_ptr);
151 floor.quest_number = QuestId::NONE;
152 player_ptr->leaving = true;
157 msg_format("%s^は天井を突き破って宙へ浮いていく。", m_name.data());
161 msg_format("%s^ rise%s up through the ceiling.", m_name.data(), (m_idx <= 0) ? "" : "s");
167 exe_write_diary(floor, DiaryKind::TELEPORT_LEVEL, -1);
171 do_cmd_save_game(player_ptr, true);
174 fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::UP, FloorChangeMode::RANDOM_PLACE, FloorChangeMode::RANDOM_CONNECT });
175 player_ptr->leaving = true;
180 msg_format("%s^は床を突き破って沈んでいく。", m_name.data());
184 msg_format("%s^ sink%s through the floor.", m_name.data(), (m_idx <= 0) ? "" : "s");
190 exe_write_diary(floor, DiaryKind::TELEPORT_LEVEL, 1);
193 do_cmd_save_game(player_ptr, true);
196 fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::DOWN, FloorChangeMode::RANDOM_PLACE, FloorChangeMode::RANDOM_CONNECT });
197 player_ptr->leaving = true;
202 sound(SOUND_TPLEVEL);
206 auto *m_ptr = &floor.m_list[m_idx];
207 QuestCompletionChecker(player_ptr, m_ptr).complete();
208 if (record_named_pet && m_ptr->is_named_pet()) {
209 const auto m2_name = monster_desc(player_ptr, m_ptr, MD_INDEF_VISIBLE);
210 exe_write_diary(floor, DiaryKind::NAMED_PET, RECORD_NAMED_PET_TELE_LEVEL, m2_name);
213 delete_monster_idx(player_ptr, m_idx);
215 sound(SOUND_TPLEVEL);
219 bool teleport_level_other(PlayerType *player_ptr)
221 if (!target_set(player_ptr, TARGET_KILL)) {
225 const auto &floor = *player_ptr->current_floor_ptr;
226 const Pos2D pos(target_row, target_col);
227 const auto &grid = floor.get_grid(pos);
228 const auto target_m_idx = grid.m_idx;
232 if (!grid.has_los()) {
235 if (!projectable(player_ptr, player_ptr->y, player_ptr->x, target_row, target_col)) {
239 const auto &monster = floor.m_list[target_m_idx];
240 const auto &monrace = monster.get_monrace();
241 const auto m_name = monster_desc(player_ptr, &monster, 0);
242 msg_format(_("%s^の足を指さした。", "You gesture at %s^'s feet."), m_name.data());
244 auto has_immune = monrace.resistance_flags.has_any_of(RFR_EFF_RESIST_NEXUS_MASK) || monrace.resistance_flags.has(MonsterResistanceType::RESIST_TELEPORT);
245 if (has_immune || (monrace.misc_flags.has(MonsterMiscType::QUESTOR)) || (monrace.level + randint1(50) > player_ptr->lev + randint1(60))) {
246 msg_print(_("しかし効果がなかった!", format("%s^ is unaffected!", m_name.data())));
248 teleport_level(player_ptr, target_m_idx);
255 * @brief 町間のテレポートを行うメインルーチン
256 * @param player_ptr プレイヤーへの参照ポインタ
257 * @return テレポート処理を決定したか否か
259 bool tele_town(PlayerType *player_ptr)
261 if (player_ptr->current_floor_ptr->dun_level) {
262 msg_print(_("この魔法は地上でしか使えない!", "This spell can only be used on the surface!"));
266 if (player_ptr->current_floor_ptr->inside_arena || AngbandSystem::get_instance().is_phase_out()) {
267 msg_print(_("この魔法は外でしか使えない!", "This spell can only be used outside!"));
275 const int towns_size = towns_info.size();
276 for (auto i = 1; i < towns_size; i++) {
277 if ((i == VALID_TOWNS) || (i == SECRET_TOWN) || (i == player_ptr->town_num) || !(player_ptr->visit & (1UL << (i - 1)))) {
281 const auto buf = format("%c) %-20s", I2A(i - 1), towns_info[i].name.data());
287 msg_print(_("まだ行けるところがない。", "You have not yet visited any town."));
293 prt(_("どこに行きますか:", "Where do you want to go: "), 0, 0);
303 if ((key < 'a') || (key > ('a' + towns_size - 2))) {
307 const auto town_num = key - 'a' + 1;
308 if ((town_num == player_ptr->town_num) || (town_num == VALID_TOWNS) || (town_num == SECRET_TOWN) || !(player_ptr->visit & (1UL << (key - 'a')))) {
315 const auto &world = AngbandWorld::get_instance();
316 for (POSITION y = 0; y < world.max_wild_y; y++) {
317 for (POSITION x = 0; x < world.max_wild_x; x++) {
318 if (wilderness[y][x].town == (key - 'a' + 1)) {
319 player_ptr->wilderness_y = y;
320 player_ptr->wilderness_x = x;
325 player_ptr->leaving = true;
326 player_ptr->teleport_town = true;
333 * @param player_ptr プレイヤーへの参照ポインタ
335 void reserve_alter_reality(PlayerType *player_ptr, TIME_EFFECT turns)
337 if (player_ptr->current_floor_ptr->inside_arena || ironman_downward) {
338 msg_print(_("何も起こらなかった。", "Nothing happens."));
342 auto &rfu = RedrawingFlagsUpdater::get_instance();
343 if (player_ptr->alter_reality || turns == 0) {
344 player_ptr->alter_reality = 0;
345 msg_print(_("景色が元に戻った...", "The view around you returns to normal..."));
346 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
350 player_ptr->alter_reality = turns;
351 msg_print(_("回りの景色が変わり始めた...", "The view around you begins to change..."));
352 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
356 * @brief これまでに入ったダンジョンの一覧を表示し、選択させる。
357 * @param note ダンジョンに施す処理記述
360 * @return 選択されたダンジョンID
362 static DUNGEON_IDX choose_dungeon(concptr note, POSITION y, POSITION x)
364 DUNGEON_IDX select_dungeon;
365 if (lite_town || vanilla_town || ironman_downward) {
366 if (max_dlv[DUNGEON_ANGBAND]) {
367 return DUNGEON_ANGBAND;
369 msg_format(_("まだ%sに入ったことはない。", "You haven't entered %s yet."), dungeons_info[DUNGEON_ANGBAND].name.data());
375 std::vector<DUNGEON_IDX> dun;
378 for (const auto &dungeon : dungeons_info) {
379 auto is_conquered = false;
380 if (!dungeon.is_dungeon() || !dungeon.maxdepth) {
384 if (!max_dlv[dungeon.idx]) {
388 if (dungeon.has_guardian()) {
389 if (dungeon.get_guardian().max_num == 0) {
392 } else if (max_dlv[dungeon.idx] == dungeon.maxdepth) {
396 constexpr auto fmt = _(" %c) %c%-12s : 最大 %d 階", " %c) %c%-16s : Max level %d");
397 const auto buf = format(fmt, static_cast<char>('a' + dun.size()), is_conquered ? '!' : ' ', dungeon.name.data(), (int)max_dlv[dungeon.idx]);
398 prt(buf, y + dun.size(), x);
399 dun.push_back(dungeon.idx);
403 prt(_(" 選べるダンジョンがない。", " No dungeon is available."), y, x);
406 prt(format(_("どのダンジョン%sしますか:", "Which dungeon do you %s?: "), note), 0, 0);
409 if ((i == ESCAPE) || dun.empty()) {
413 if (i >= 'a' && i < static_cast<char>('a' + dun.size())) {
414 select_dungeon = dun[i - 'a'];
422 return select_dungeon;
426 * @brief プレイヤーの帰還発動及び中止処理 /
427 * Recall the player to town or dungeon
428 * @param player_ptr プレイヤーへの参照ポインタ
429 * @param turns 発動までのターン数
431 * @todo Recall the player to the last visited town when in the wilderness
433 bool recall_player(PlayerType *player_ptr, TIME_EFFECT turns)
435 const auto &floor = *player_ptr->current_floor_ptr;
436 if (floor.inside_arena || ironman_downward) {
437 msg_print(_("何も起こらなかった。", "Nothing happens."));
441 bool is_special_floor = floor.is_in_underground();
442 is_special_floor &= max_dlv[floor.dungeon_idx] > floor.dun_level;
443 is_special_floor &= !floor.is_in_quest();
444 is_special_floor &= !player_ptr->word_recall;
445 if (is_special_floor) {
446 if (input_check(_("ここは最深到達階より浅い階です。この階に戻って来ますか? ", "Reset recall depth? "))) {
447 max_dlv[floor.dungeon_idx] = floor.dun_level;
448 if (record_maxdepth) {
449 exe_write_diary(floor, DiaryKind::TRUMP, floor.dungeon_idx, _("帰還のときに", "when recalled from dungeon"));
454 auto &rfu = RedrawingFlagsUpdater::get_instance();
455 if (player_ptr->word_recall || turns == 0) {
456 player_ptr->word_recall = 0;
457 msg_print(_("張りつめた大気が流れ去った...", "A tension leaves the air around you..."));
458 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
462 if (!floor.is_in_underground()) {
463 DUNGEON_IDX select_dungeon;
464 select_dungeon = choose_dungeon(_("に帰還", "recall"), 2, 14);
465 if (!select_dungeon) {
468 player_ptr->recall_dungeon = select_dungeon;
471 player_ptr->word_recall = turns;
472 msg_print(_("回りの大気が張りつめてきた...", "The air about you becomes charged..."));
473 rfu.set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
477 bool free_level_recall(PlayerType *player_ptr)
479 DUNGEON_IDX select_dungeon = choose_dungeon(_("にテレポート", "teleport"), 4, 0);
480 if (!select_dungeon) {
484 const auto &dungeon = dungeons_info[select_dungeon];
485 auto max_depth = dungeon.maxdepth;
486 if (select_dungeon == DUNGEON_ANGBAND) {
487 const auto &quests = QuestList::get_instance();
488 if (quests.get_quest(QuestId::OBERON).status != QuestStatusType::FINISHED) {
490 } else if (quests.get_quest(QuestId::SERPENT).status != QuestStatusType::FINISHED) {
495 const auto mes = _("%sの何階にテレポートしますか?", "Teleport to which level of %s? ");
496 const auto amt = input_quantity(max_depth, format(mes, dungeon.name.data()));
501 player_ptr->word_recall = 1;
502 player_ptr->recall_dungeon = select_dungeon;
503 max_dlv[player_ptr->recall_dungeon] = ((amt > dungeon.maxdepth) ? dungeon.maxdepth
504 : ((amt < dungeon.mindepth) ? dungeon.mindepth : amt));
505 if (record_maxdepth) {
506 exe_write_diary(*player_ptr->current_floor_ptr, DiaryKind::TRUMP, select_dungeon, _("トランプタワーで", "at Trump Tower"));
509 msg_print(_("回りの大気が張りつめてきた...", "The air about you becomes charged..."));
510 RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::TIMED_EFFECT);
516 * @param player_ptr プレイヤーへの参照ポインタ
517 * @return リセット処理が実際に行われたらTRUEを返す
519 bool reset_recall(PlayerType *player_ptr)
521 auto select_dungeon = choose_dungeon(_("をセット", "reset"), 2, 14);
522 if (ironman_downward) {
523 msg_print(_("何も起こらなかった。", "Nothing happens."));
527 if (!select_dungeon) {
531 constexpr auto prompt = _("何階にセットしますか?", "Reset to which level?");
532 const auto min_level = dungeons_info[select_dungeon].mindepth;
533 const auto max_level = max_dlv[select_dungeon];
534 const auto reset_level = input_numerics(prompt, min_level, max_level, max_level);
539 max_dlv[select_dungeon] = *reset_level;
540 if (record_maxdepth) {
541 constexpr auto note = _("フロア・リセットで", "using a scroll of reset recall");
542 exe_write_diary(*player_ptr->current_floor_ptr, DiaryKind::TRUMP, select_dungeon, note);
545 msg_format("%sの帰還レベルを %d 階にセット。", dungeons_info[select_dungeon].name.data(), *reset_level);
547 msg_format("Recall depth set to level %d (%d').", *reset_level, *reset_level * 50);