OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[hengbandforosx/hengbandosx.git] / src / store / cmd-store.cpp
1 #include "store/cmd-store.h"
2 #include "cmd-io/macro-util.h"
3 #include "core/stuff-handler.h"
4 #include "core/window-redrawer.h"
5 #include "flavor/flavor-describer.h"
6 #include "floor/cave.h"
7 #include "floor/floor-events.h"
8 #include "floor/floor-town.h"
9 #include "game-option/birth-options.h"
10 #include "game-option/input-options.h"
11 #include "inventory/inventory-object.h"
12 #include "inventory/inventory-slot-types.h"
13 #include "io/input-key-requester.h"
14 #include "main/music-definitions-table.h"
15 #include "main/sound-of-music.h"
16 #include "object/object-info.h"
17 #include "player-status/player-energy.h"
18 #include "store/home.h"
19 #include "store/store-key-processor.h"
20 #include "store/store-owners.h"
21 #include "store/store-util.h"
22 #include "store/store.h"
23 #include "system/dungeon-info.h"
24 #include "system/floor-type-definition.h"
25 #include "system/grid-type-definition.h"
26 #include "system/item-entity.h"
27 #include "system/player-type-definition.h"
28 #include "system/redrawing-flags-updater.h"
29 #include "system/terrain-type-definition.h"
30 #include "term/gameterm.h"
31 #include "term/screen-processor.h"
32 #include "util/bit-flags-calculator.h"
33 #include "view/display-messages.h"
34 #include "view/display-store.h"
35 #include "world/world.h"
36
37 #define MIN_STOCK 12
38
39 /*!
40  * @brief 店舗処理全体のメインルーチン /
41  * Enter a store, and interact with it. *
42  * @param player_ptr プレイヤーへの参照ポインタ
43  * @note
44  * <pre>
45  * Note that we use the standard "request_command()" function
46  * to get a command, allowing us to use "command_arg" and all
47  * command macros and other nifty stuff, but we use the special
48  * "shopping" argument, to force certain commands to be converted
49  * into other commands, normally, we convert "p" (pray) and "m"
50  * (cast magic) into "g" (get), and "s" (search) into "d" (drop).
51  * </pre>
52  */
53 void do_cmd_store(PlayerType *player_ptr)
54 {
55     if (player_ptr->wild_mode) {
56         return;
57     }
58     TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, std::nullopt);
59     const auto [wid, hgt] = term_get_size();
60     xtra_stock = std::min(14 + 26, ((hgt > MAIN_TERM_MIN_ROWS) ? (hgt - MAIN_TERM_MIN_ROWS) : 0));
61     store_bottom = MIN_STOCK + xtra_stock;
62
63     auto *floor_ptr = player_ptr->current_floor_ptr;
64     const auto *g_ptr = &floor_ptr->grid_array[player_ptr->y][player_ptr->x];
65     if (!g_ptr->cave_has_flag(TerrainCharacteristics::STORE)) {
66         msg_print(_("ここには店がありません。", "You see no store here."));
67         return;
68     }
69
70     // TODO:
71     //   施設の種類により、一時的に現在地 (player_ptr->town_num) を違う値に偽装して処理している。
72     //   我が家および博物館は全ての町で内容を共有するため、現在地を辺境の地 (1) にしている。
73     //   ダンジョン内の店の場合、現在地を NO_TOWN にしている。
74     //   inner_town_num は、施設内で C コマンドなどを使ったときにそのままでは現在地の偽装がバレる
75     //   ため、それを糊塗するためのグローバル変数。
76     //   この辺はリファクタしたい。
77     StoreSaleType store_num = i2enum<StoreSaleType>(terrains_info[g_ptr->feat].subtype);
78     old_town_num = player_ptr->town_num;
79     if ((store_num == StoreSaleType::HOME) || (store_num == StoreSaleType::MUSEUM)) {
80         player_ptr->town_num = 1;
81     }
82
83     if (floor_ptr->is_in_dungeon()) {
84         player_ptr->town_num = VALID_TOWNS;
85     }
86
87     inner_town_num = player_ptr->town_num;
88     auto &town = towns_info[player_ptr->town_num];
89     auto &store = town.stores[store_num];
90     if ((store.store_open >= w_ptr->game_turn) || ironman_shops) {
91         msg_print(_("ドアに鍵がかかっている。", "The doors are locked."));
92         player_ptr->town_num = old_town_num;
93         return;
94     }
95
96     int maintain_num = (w_ptr->game_turn - store.last_visit) / (TURNS_PER_TICK * STORE_TICKS);
97     if (maintain_num > 10) {
98         maintain_num = 10;
99     }
100     if (maintain_num) {
101         store_maintenance(player_ptr, player_ptr->town_num, store_num, maintain_num);
102
103         store.last_visit = w_ptr->game_turn;
104     }
105
106     forget_lite(floor_ptr);
107     forget_view(floor_ptr);
108     w_ptr->character_icky_depth = 1;
109     command_arg = 0;
110     command_rep = 0;
111     command_new = 0;
112     get_com_no_macros = true;
113     cur_store_feat = g_ptr->feat;
114     st_ptr = &towns_info[player_ptr->town_num].stores[store_num];
115     ot_ptr = &owners.at(store_num)[st_ptr->owner];
116     store_top = 0;
117     play_music(TERM_XTRA_MUSIC_BASIC, MUSIC_BASIC_BUILD);
118     display_store(player_ptr, store_num);
119     leave_store = false;
120     auto &rfu = RedrawingFlagsUpdater::get_instance();
121     while (!leave_store) {
122         prt("", 1, 0);
123         clear_from(20 + xtra_stock);
124         prt(_(" ESC) 建物から出る", " ESC) Exit from Building."), 21 + xtra_stock, 0);
125         if (st_ptr->stock_num > store_bottom) {
126             prt(_(" -)前ページ", " -) Previous page"), 22 + xtra_stock, 0);
127             prt(_(" スペース) 次ページ", " SPACE) Next page"), 23 + xtra_stock, 0);
128         }
129
130         if (store_num == StoreSaleType::HOME) {
131             prt(_("g) アイテムを取る", "g) Get an item."), 21 + xtra_stock, 27);
132             prt(_("d) アイテムを置く", "d) Drop an item."), 22 + xtra_stock, 27);
133             prt(_("x) 家のアイテムを調べる", "x) eXamine an item in the home."), 23 + xtra_stock, 27);
134         } else if (store_num == StoreSaleType::MUSEUM) {
135             prt(_("d) アイテムを置く", "d) Drop an item."), 21 + xtra_stock, 27);
136             prt(_("r) アイテムの展示をやめる", "r) order to Remove an item."), 22 + xtra_stock, 27);
137             prt(_("x) 博物館のアイテムを調べる", "x) eXamine an item in the museum."), 23 + xtra_stock, 27);
138         } else {
139             prt(_("p) 商品を買う", "p) Purchase an item."), 21 + xtra_stock, 30);
140             prt(_("s) アイテムを売る", "s) Sell an item."), 22 + xtra_stock, 30);
141             prt(_("x) 商品を調べる", "x) eXamine an item in the shop"), 23 + xtra_stock, 30);
142         }
143
144         prt(_("i/e) 持ち物/装備の一覧", "i/e) Inventry/Equipment list"), 21 + xtra_stock, 56);
145         if (rogue_like_commands) {
146             prt(_("w/T) 装備する/はずす", "w/T) Wear/Take off equipment"), 22 + xtra_stock, 56);
147         } else {
148             prt(_("w/t) 装備する/はずす", "w/t) Wear/Take off equipment"), 22 + xtra_stock, 56);
149         }
150
151         prt(_("コマンド:", "You may: "), 20 + xtra_stock, 0);
152         InputKeyRequestor(player_ptr, true).request_command();
153         store_process_command(player_ptr, store_num);
154
155         const auto should_redraw_store_inventory = rfu.has(StatusRecalculatingFlag::BONUS);
156         w_ptr->character_icky_depth = 1;
157         handle_stuff(player_ptr);
158         if (player_ptr->inventory_list[INVEN_PACK].bi_id) {
159             INVENTORY_IDX item = INVEN_PACK;
160             auto *o_ptr = &player_ptr->inventory_list[item];
161             if (store_num != StoreSaleType::HOME) {
162                 if (store_num == StoreSaleType::MUSEUM) {
163                     msg_print(_("ザックからアイテムがあふれそうなので、あわてて博物館から出た...", "Your pack is so full that you flee the Museum..."));
164                 } else {
165                     msg_print(_("ザックからアイテムがあふれそうなので、あわてて店から出た...", "Your pack is so full that you flee the store..."));
166                 }
167
168                 leave_store = true;
169             } else if (!store_check_num(o_ptr, store_num)) {
170                 msg_print(_("ザックからアイテムがあふれそうなので、あわてて家から出た...", "Your pack is so full that you flee your home..."));
171                 leave_store = true;
172             } else {
173                 int item_pos;
174                 ItemEntity forge;
175                 ItemEntity *q_ptr;
176                 msg_print(_("ザックからアイテムがあふれてしまった!", "Your pack overflows!"));
177                 q_ptr = &forge;
178                 q_ptr->copy_from(o_ptr);
179                 const auto item_name = describe_flavor(player_ptr, q_ptr, 0);
180                 msg_format(_("%sが落ちた。(%c)", "You drop %s (%c)."), item_name.data(), index_to_label(item));
181                 vary_item(player_ptr, item, -255);
182                 handle_stuff(player_ptr);
183
184                 item_pos = home_carry(player_ptr, q_ptr, store_num);
185                 if (item_pos >= 0) {
186                     store_top = (item_pos / store_bottom) * store_bottom;
187                     display_store_inventory(player_ptr, store_num);
188                 }
189             }
190         }
191
192         if (should_redraw_store_inventory) {
193             display_store_inventory(player_ptr, store_num);
194         }
195
196         if (st_ptr->store_open >= w_ptr->game_turn) {
197             leave_store = true;
198         }
199     }
200
201     // 現在地の偽装を解除。
202     player_ptr->town_num = old_town_num;
203
204     select_floor_music(player_ptr);
205     PlayerEnergy(player_ptr).set_player_turn_energy(100);
206     w_ptr->character_icky_depth = 0;
207     command_new = 0;
208     command_see = false;
209     get_com_no_macros = false;
210
211     msg_erase();
212     term_clear();
213
214     static constexpr auto flags_srf = {
215         StatusRecalculatingFlag::VIEW,
216         StatusRecalculatingFlag::LITE,
217         StatusRecalculatingFlag::MONSTER_LITE,
218         StatusRecalculatingFlag::MONSTER_STATUSES,
219     };
220     rfu.set_flags(flags_srf);
221     static constexpr auto flags_mwrf = {
222         MainWindowRedrawingFlag::BASIC,
223         MainWindowRedrawingFlag::EXTRA,
224         MainWindowRedrawingFlag::EQUIPPY,
225         MainWindowRedrawingFlag::MAP,
226     };
227     rfu.set_flags(flags_mwrf);
228     static constexpr auto flags_swrf = {
229         SubWindowRedrawingFlag::OVERHEAD,
230         SubWindowRedrawingFlag::DUNGEON,
231     };
232     rfu.set_flags(flags_swrf);
233 }