OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / cmd-action / cmd-move.cpp
1 #include "cmd-action/cmd-move.h"
2 #include "action/action-limited.h"
3 #include "action/movement-execution.h"
4 #include "action/run-execution.h"
5 #include "avatar/avatar.h"
6 #include "cmd-io/cmd-save.h"
7 #include "core/asking-player.h"
8 #include "core/disturbance.h"
9 #include "core/stuff-handler.h"
10 #include "dungeon/quest.h"
11 #include "floor/cave.h"
12 #include "floor/floor-mode-changer.h"
13 #include "floor/wild.h"
14 #include "game-option/birth-options.h"
15 #include "game-option/input-options.h"
16 #include "game-option/map-screen-options.h"
17 #include "game-option/play-record-options.h"
18 #include "game-option/special-options.h"
19 #include "info-reader/fixed-map-parser.h"
20 #include "io/input-key-requester.h"
21 #include "io/write-diary.h"
22 #include "main/sound-definitions-table.h"
23 #include "main/sound-of-music.h"
24 #include "mind/mind-ninja.h"
25 #include "player-base/player-class.h"
26 #include "player-info/samurai-data-type.h"
27 #include "player-status/player-energy.h"
28 #include "player/attack-defense-types.h"
29 #include "player/player-move.h"
30 #include "player/player-status.h"
31 #include "player/special-defense-types.h"
32 #include "spell-realm/spells-hex.h"
33 #include "spell-realm/spells-song.h"
34 #include "status/action-setter.h"
35 #include "system/dungeon-info.h"
36 #include "system/floor-type-definition.h"
37 #include "system/grid-type-definition.h"
38 #include "system/player-type-definition.h"
39 #include "system/redrawing-flags-updater.h"
40 #include "system/terrain-type-definition.h"
41 #include "target/target-getter.h"
42 #include "timed-effect/timed-effects.h"
43 #include "util/bit-flags-calculator.h"
44 #include "view/display-messages.h"
45 #include "world/world.h"
46 #include <algorithm>
47
48 /*!
49  * @brief フロア脱出時に出戻りが不可能だった場合に警告を加える処理
50  * @param down_stair TRUEならば階段を降りる処理、FALSEなら階段を昇る処理による内容
51  * @return フロア移動を実際に行うならTRUE、キャンセルする場合はFALSE
52  */
53 static bool confirm_leave_level(PlayerType *player_ptr, bool down_stair)
54 {
55     const auto &quests = QuestList::get_instance();
56     const auto &quest = quests.get_quest(player_ptr->current_floor_ptr->quest_number);
57
58     auto caution_in_tower = any_bits(quest.flags, QUEST_FLAG_TOWER);
59     caution_in_tower &= quest.status != QuestStatusType::STAGE_COMPLETED || (down_stair && (quests.get_quest(QuestId::TOWER1).status != QuestStatusType::COMPLETED));
60
61     auto caution_in_quest = quest.type == QuestKindType::RANDOM;
62     caution_in_quest |= quest.flags & QUEST_FLAG_ONCE && quest.status != QuestStatusType::COMPLETED;
63     caution_in_quest |= caution_in_tower;
64
65     if (confirm_quest && player_ptr->current_floor_ptr->is_in_quest() && caution_in_quest) {
66         msg_print(_("この階を一度去ると二度と戻って来られません。", "You can't come back here once you leave this floor."));
67         return input_check(_("本当にこの階を去りますか?", "Really leave this floor? "));
68     }
69
70     return true;
71 }
72
73 /*!
74  * @brief 階段を使って階層を昇る処理 / Go up one level
75  */
76 void do_cmd_go_up(PlayerType *player_ptr)
77 {
78     auto &quests = QuestList::get_instance();
79     auto &floor = *player_ptr->current_floor_ptr;
80     const auto &grid = floor.get_grid({ player_ptr->y, player_ptr->x });
81     const auto &terrain = grid.get_terrain();
82     PlayerClass(player_ptr).break_samurai_stance({ SamuraiStanceType::MUSOU });
83
84     if (terrain.flags.has_not(TerrainCharacteristics::LESS)) {
85         msg_print(_("ここには上り階段が見当たらない。", "I see no up staircase here."));
86         return;
87     }
88
89     if (terrain.flags.has(TerrainCharacteristics::QUEST)) {
90         if (!confirm_leave_level(player_ptr, false)) {
91             return;
92         }
93
94         if (is_echizen(player_ptr)) {
95             msg_print(_("なんだこの階段は!", "What's this STAIRWAY!"));
96         } else {
97             msg_print(_("上の階に登った。", "You enter the up staircase."));
98         }
99
100         sound(SOUND_STAIRWAY);
101
102         leave_quest_check(player_ptr);
103         floor.quest_number = i2enum<QuestId>(grid.special);
104         const auto quest_id = floor.quest_number;
105         auto &quest = quests.get_quest(quest_id);
106         if (quest.status == QuestStatusType::UNTAKEN) {
107             if (quest.type != QuestKindType::RANDOM) {
108                 init_flags = INIT_ASSIGN;
109                 parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
110             }
111
112             quest.status = QuestStatusType::TAKEN;
113         }
114
115         if (!inside_quest(quest_id)) {
116             floor.dun_level = 0;
117             player_ptr->word_recall = 0;
118         }
119
120         player_ptr->leaving = true;
121         player_ptr->oldpx = 0;
122         player_ptr->oldpy = 0;
123         PlayerEnergy(player_ptr).set_player_turn_energy(100);
124         return;
125     }
126
127     auto go_up = false;
128     if (!floor.is_in_underground()) {
129         go_up = true;
130     } else {
131         go_up = confirm_leave_level(player_ptr, false);
132     }
133
134     if (!go_up) {
135         return;
136     }
137
138     PlayerEnergy(player_ptr).set_player_turn_energy(100);
139
140     if (autosave_l) {
141         do_cmd_save_game(player_ptr, true);
142     }
143
144     const auto quest_number = floor.quest_number;
145     auto &quest = quests.get_quest(quest_number);
146
147     if (inside_quest(quest_number) && quest.type == QuestKindType::RANDOM) {
148         leave_quest_check(player_ptr);
149         floor.quest_number = QuestId::NONE;
150     }
151
152     auto up_num = 0;
153     if (inside_quest(quest_number) && quest.type != QuestKindType::RANDOM) {
154         leave_quest_check(player_ptr);
155         floor.quest_number = i2enum<QuestId>(grid.special);
156         floor.dun_level = 0;
157         up_num = 0;
158     } else {
159         auto &fcms = FloorChangeModesStore::get_instace();
160         fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::UP });
161         up_num = 1;
162         if (terrain.flags.has(TerrainCharacteristics::SHAFT)) {
163             fcms->set(FloorChangeMode::SHAFT);
164             up_num *= 2;
165         }
166
167         if (floor.dun_level - up_num < floor.get_dungeon_definition().mindepth) {
168             up_num = floor.dun_level;
169         }
170     }
171
172     if (record_stair) {
173         exe_write_diary(floor, DiaryKind::STAIR, 0 - up_num, _("階段を上った", "climbed up the stairs to"));
174     }
175
176     if (up_num == floor.dun_level) {
177         if (is_echizen(player_ptr)) {
178             msg_print(_("なんだこの階段は!", "What's this STAIRWAY!"));
179         } else {
180             msg_print(_("地上に戻った。", "You go back to the surface."));
181         }
182         player_ptr->word_recall = 0;
183     } else {
184         if (is_echizen(player_ptr)) {
185             msg_print(_("なんだこの階段は!", "What's this STAIRWAY!"));
186         } else {
187             msg_print(_("階段を上って新たなる迷宮へと足を踏み入れた。", "You enter a maze of up staircases."));
188         }
189     }
190
191     sound(SOUND_STAIRWAY);
192
193     player_ptr->leaving = true;
194 }
195
196 /*!
197  * @brief 階段を使って階層を降りる処理 / Go down one level
198  * @param player_ptr プレイヤーへの参照ポインタ
199  */
200 void do_cmd_go_down(PlayerType *player_ptr)
201 {
202     bool fall_trap = false;
203     int down_num = 0;
204     PlayerClass(player_ptr).break_samurai_stance({ SamuraiStanceType::MUSOU });
205
206     auto &floor = *player_ptr->current_floor_ptr;
207     auto &grid = floor.grid_array[player_ptr->y][player_ptr->x];
208     auto &terrain = grid.get_terrain();
209     if (terrain.flags.has_not(TerrainCharacteristics::MORE)) {
210         msg_print(_("ここには下り階段が見当たらない。", "I see no down staircase here."));
211         return;
212     }
213
214     if (terrain.flags.has(TerrainCharacteristics::TRAP)) {
215         fall_trap = true;
216     }
217
218     if (terrain.flags.has(TerrainCharacteristics::QUEST_ENTER)) {
219         do_cmd_quest(player_ptr);
220         return;
221     }
222
223     if (terrain.flags.has(TerrainCharacteristics::QUEST)) {
224         if (!confirm_leave_level(player_ptr, true)) {
225             return;
226         }
227
228         if (is_echizen(player_ptr)) {
229             msg_print(_("なんだこの階段は!", "What's this STAIRWAY!"));
230         } else {
231             msg_print(_("下の階に降りた。", "You enter the down staircase."));
232         }
233
234         sound(SOUND_STAIRWAY);
235
236         leave_quest_check(player_ptr);
237         leave_tower_check(player_ptr);
238         floor.quest_number = i2enum<QuestId>(grid.special);
239
240         auto &quests = QuestList::get_instance();
241         auto &quest = quests.get_quest(floor.quest_number);
242         if (quest.status == QuestStatusType::UNTAKEN) {
243             if (quest.type != QuestKindType::RANDOM) {
244                 init_flags = INIT_ASSIGN;
245                 parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
246             }
247
248             quest.status = QuestStatusType::TAKEN;
249         }
250
251         if (!floor.is_in_quest()) {
252             floor.dun_level = 0;
253             player_ptr->word_recall = 0;
254         }
255
256         player_ptr->leaving = true;
257         player_ptr->oldpx = 0;
258         player_ptr->oldpy = 0;
259         PlayerEnergy(player_ptr).set_player_turn_energy(100);
260         return;
261     }
262
263     short target_dungeon = 0;
264     auto &fcms = FloorChangeModesStore::get_instace();
265     if (!floor.is_in_underground()) {
266         target_dungeon = terrain.flags.has(TerrainCharacteristics::ENTRANCE) ? grid.special : DUNGEON_ANGBAND;
267         if (ironman_downward && (target_dungeon != DUNGEON_ANGBAND)) {
268             msg_print(_("ダンジョンの入口は塞がれている!", "The entrance of this dungeon is closed!"));
269             return;
270         }
271
272         if (!max_dlv[target_dungeon]) {
273             const auto mes = _("ここには%sの入り口(%d階相当)があります", "There is the entrance of %s (Danger level: %d)");
274             const auto &dungeon = dungeons_info[target_dungeon];
275             msg_format(mes, dungeon.name.data(), dungeon.mindepth);
276             if (!input_check(_("本当にこのダンジョンに入りますか?", "Do you really get in this dungeon? "))) {
277                 return;
278             }
279         }
280
281         player_ptr->oldpx = player_ptr->x;
282         player_ptr->oldpy = player_ptr->y;
283         floor.set_dungeon_index(target_dungeon);
284         fcms->set(FloorChangeMode::FIRST_FLOOR);
285     }
286
287     PlayerEnergy(player_ptr).set_player_turn_energy(100);
288     if (autosave_l) {
289         do_cmd_save_game(player_ptr, true);
290     }
291
292     if (terrain.flags.has(TerrainCharacteristics::SHAFT)) {
293         down_num += 2;
294     } else {
295         down_num += 1;
296     }
297
298     const auto &dungeon = floor.get_dungeon_definition();
299     if (!floor.is_in_underground()) {
300         player_ptr->enter_dungeon = true;
301         down_num = dungeon.mindepth;
302     }
303
304     if (record_stair) {
305         if (fall_trap) {
306             exe_write_diary(floor, DiaryKind::STAIR, down_num, _("落とし戸に落ちた", "fell through a trap door"));
307         } else {
308             exe_write_diary(floor, DiaryKind::STAIR, down_num, _("階段を下りた", "climbed down the stairs to"));
309         }
310     }
311
312     if (fall_trap) {
313         msg_print(_("わざと落とし戸に落ちた。", "You deliberately jump through the trap door."));
314     } else {
315         if (target_dungeon) {
316             msg_format(_("%sへ入った。", "You entered %s."), dungeon.text.data());
317         } else {
318             if (is_echizen(player_ptr)) {
319                 msg_print(_("なんだこの階段は!", "What's this STAIRWAY!"));
320             } else {
321                 msg_print(_("階段を下りて新たなる迷宮へと足を踏み入れた。", "You enter a maze of down staircases."));
322             }
323         }
324
325         sound(SOUND_STAIRWAY);
326     }
327
328     player_ptr->leaving = true;
329
330     if (fall_trap) {
331         fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::DOWN, FloorChangeMode::RANDOM_PLACE, FloorChangeMode::RANDOM_CONNECT });
332         return;
333     }
334
335     fcms->set({ FloorChangeMode::SAVE_FLOORS, FloorChangeMode::DOWN });
336     if (terrain.flags.has(TerrainCharacteristics::SHAFT)) {
337         fcms->set(FloorChangeMode::SHAFT);
338     }
339 }
340
341 /*!
342  * @brief 「歩く」動作コマンドのメインルーチン /
343  * Support code for the "Walk" and "Jump" commands
344  * @param player_ptr プレイヤーへの参照ポインタ
345  * @param pickup アイテムの自動拾いを行うならTRUE
346  */
347 void do_cmd_walk(PlayerType *player_ptr, bool pickup)
348 {
349     if (command_arg) {
350         command_rep = command_arg - 1;
351         RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::ACTION);
352         command_arg = 0;
353     }
354
355     bool more = false;
356     DIRECTION dir;
357     const auto is_wild_mode = AngbandWorld::get_instance().is_wild_mode();
358     if (get_rep_dir(player_ptr, &dir)) {
359         PlayerEnergy energy(player_ptr);
360         energy.set_player_turn_energy(100);
361         if (dir != 5) {
362             PlayerClass(player_ptr).break_samurai_stance({ SamuraiStanceType::MUSOU });
363         }
364
365         if (is_wild_mode) {
366             energy.mul_player_turn_energy((MAX_HGT + MAX_WID) / 2);
367         }
368
369         if (player_ptr->action == ACTION_HAYAGAKE) {
370             auto energy_use = (ENERGY)(player_ptr->energy_use * (45 - (player_ptr->lev / 2)) / 100);
371             energy.set_player_turn_energy(energy_use);
372         }
373
374         exe_movement(player_ptr, dir, pickup, false);
375         more = true;
376     }
377
378     if (is_wild_mode && !cave_has_flag_bold(player_ptr->current_floor_ptr, player_ptr->y, player_ptr->x, TerrainCharacteristics::TOWN)) {
379         int tmp = 120 + player_ptr->lev * 10 - wilderness[player_ptr->y][player_ptr->x].level + 5;
380         if (tmp < 1) {
381             tmp = 1;
382         }
383
384         if (((wilderness[player_ptr->y][player_ptr->x].level + 5) > (player_ptr->lev / 2)) && randint0(tmp) < (21 - player_ptr->skill_stl)) {
385             msg_print(_("襲撃だ!", "You are ambushed !"));
386             player_ptr->oldpy = randint1(MAX_HGT - 2);
387             player_ptr->oldpx = randint1(MAX_WID - 2);
388             change_wild_mode(player_ptr, true);
389             PlayerEnergy(player_ptr).set_player_turn_energy(100);
390         }
391     }
392
393     if (!more) {
394         disturb(player_ptr, false, false);
395     }
396 }
397
398 /*!
399  * @brief 「走る」動作コマンドのメインルーチン /
400  * Start running.
401  * @param player_ptr プレイヤーへの参照ポインタ
402  */
403 void do_cmd_run(PlayerType *player_ptr)
404 {
405     DIRECTION dir;
406     if (cmd_limit_confused(player_ptr)) {
407         return;
408     }
409
410     PlayerClass(player_ptr).break_samurai_stance({ SamuraiStanceType::MUSOU });
411
412     if (get_rep_dir(player_ptr, &dir)) {
413         player_ptr->running = (command_arg ? command_arg : 1000);
414         run_step(player_ptr, dir);
415     }
416 }
417
418 /*!
419  * @brief 「留まる」動作コマンドのメインルーチン /
420  * Stay still.  Search.  Enter stores.
421  * Pick up treasure if "pickup" is true.
422  * @param player_ptr プレイヤーへの参照ポインタ
423  * @param pickup アイテムの自動拾いを行うならTRUE
424  */
425 void do_cmd_stay(PlayerType *player_ptr, bool pickup)
426 {
427     uint32_t mpe_mode = MPE_STAYING | MPE_ENERGY_USE;
428     if (command_arg) {
429         command_rep = command_arg - 1;
430         RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::ACTION);
431         command_arg = 0;
432     }
433
434     PlayerEnergy(player_ptr).set_player_turn_energy(100);
435     if (pickup) {
436         mpe_mode |= MPE_DO_PICKUP;
437     }
438
439     (void)move_player_effect(player_ptr, player_ptr->y, player_ptr->x, mpe_mode);
440 }
441
442 /*!
443  * @brief 休憩ターン数のコマンド受付
444  */
445 static bool input_rest_turns()
446 {
447     constexpr auto p = _("休憩 (0-9999, '*' で HP/MP全快, '&' で必要なだけ): ", "Rest (0-9999, '*' for HP/SP, '&' as needed): ");
448     while (true) {
449         const auto rest_turns = input_string(p, 4, "&");
450         if (!rest_turns) {
451             return false;
452         }
453
454         if (rest_turns->starts_with('&')) {
455             command_arg = COMMAND_ARG_REST_UNTIL_DONE;
456             return true;
457         }
458
459         if (rest_turns->starts_with('*')) {
460             command_arg = COMMAND_ARG_REST_FULL_HEALING;
461             return true;
462         }
463
464         try {
465             command_arg = static_cast<short>(std::clamp(std::stoi(*rest_turns), 0, 9999));
466             return true;
467         } catch (std::invalid_argument const &) {
468             msg_print(_("数値を入力して下さい。", "Please input numeric value."));
469         }
470     }
471 }
472
473 /*!
474  * @brief 「休む」動作コマンドのメインルーチン /
475  * Resting allows a player to safely restore his hp     -RAK-
476  * @param player_ptr プレイヤーへの参照ポインタ
477  */
478 void do_cmd_rest(PlayerType *player_ptr)
479 {
480     set_action(player_ptr, ACTION_NONE);
481     if (PlayerClass(player_ptr).equals(PlayerClassType::BARD)) {
482         auto is_singing = get_singing_song_effect(player_ptr) != 0;
483         is_singing |= get_interrupting_song_effect(player_ptr) != 0;
484         if (is_singing) {
485             stop_singing(player_ptr);
486         }
487     }
488
489     SpellHex spell_hex(player_ptr);
490     if (spell_hex.is_spelling_any()) {
491         (void)spell_hex.stop_all_spells();
492     }
493
494     if (!input_rest_turns()) {
495         return;
496     }
497
498     set_superstealth(player_ptr, false);
499
500     PlayerEnergy(player_ptr).set_player_turn_energy(100);
501     if (command_arg > 100) {
502         chg_virtue(player_ptr, Virtue::DILIGENCE, -1);
503     }
504
505     if (player_ptr->is_fully_healthy()) {
506         chg_virtue(player_ptr, Virtue::DILIGENCE, -1);
507     }
508
509     player_ptr->resting = command_arg;
510     player_ptr->action = ACTION_REST;
511     auto &rfu = RedrawingFlagsUpdater::get_instance();
512     rfu.set_flag(StatusRecalculatingFlag::BONUS);
513     rfu.set_flag(MainWindowRedrawingFlag::ACTION);
514     handle_stuff(player_ptr);
515     term_fresh();
516 }