OSDN Git Service

d11c433d026bce42923fabbc71308ea18cb200fe
[hengbandforosx/hengbandosx.git] / src / market / bounty.cpp
1 #include "market/bounty.h"
2 #include "autopick/autopick.h"
3 #include "avatar/avatar.h"
4 #include "cmd-building/cmd-building.h"
5 #include "core/asking-player.h"
6 #include "core/player-redraw-types.h"
7 #include "core/stuff-handler.h"
8 #include "flavor/flavor-describer.h"
9 #include "game-option/cheat-options.h"
10 #include "inventory/inventory-object.h"
11 #include "inventory/inventory-slot-types.h"
12 #include "io/input-key-acceptor.h"
13 #include "market/bounty-prize-table.h"
14 #include "market/building-util.h"
15 #include "monster-race/monster-race-hook.h"
16 #include "monster-race/monster-race.h"
17 #include "monster-race/race-flags1.h"
18 #include "monster-race/race-flags2.h"
19 #include "monster-race/race-flags7.h"
20 #include "monster-race/race-indice-types.h"
21 #include "monster/monster-list.h"
22 #include "monster/monster-util.h"
23 #include "object-enchant/item-apply-magic.h"
24 #include "object-enchant/item-magic-applier.h"
25 #include "object/object-info.h"
26 #include "object/object-kind-hook.h"
27 #include "perception/object-perception.h"
28 #include "sv-definition/sv-other-types.h"
29 #include "system/baseitem-info.h"
30 #include "system/dungeon-info.h"
31 #include "system/floor-type-definition.h"
32 #include "system/item-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 "term/screen-processor.h"
37 #include "term/term-color-types.h"
38 #include "term/z-form.h"
39 #include "util/bit-flags-calculator.h"
40 #include "view/display-messages.h"
41 #include "world/world.h"
42 #include <algorithm>
43
44 /*!
45  * @brief 賞金首の引き換え処理 / Get prize
46  * @param player_ptr プレイヤーへの参照ポインタ
47  * @return 各種賞金首のいずれかでも換金が行われたか否か。
48  */
49 bool exchange_cash(PlayerType *player_ptr)
50 {
51     auto change = false;
52     auto &rfu = RedrawingFlagsUpdater::get_instance();
53     for (INVENTORY_IDX i = 0; i <= INVEN_SUB_HAND; i++) {
54         const auto item_ptr = &player_ptr->inventory_list[i];
55         const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
56         if ((item_ptr->bi_key.tval() != ItemKindType::CAPTURE) || (r_idx_of_item != MonsterRaceId::TSUCHINOKO)) {
57             continue;
58         }
59
60         change = true;
61         const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
62         if (!get_check(format(_("%s を換金しますか?", "Convert %s into money? "), item_name.data()))) {
63             continue;
64         }
65
66         msg_format(_("賞金 %ld$を手に入れた。", "You get %ldgp."), (long int)(1000000L * item_ptr->number));
67         player_ptr->au += 1000000L * item_ptr->number;
68         rfu.set_flag(MainWindowRedrawingFlag::GOLD);
69         vary_item(player_ptr, i, -item_ptr->number);
70     }
71
72     for (INVENTORY_IDX i = 0; i < INVEN_PACK; i++) {
73         const auto item_ptr = &player_ptr->inventory_list[i];
74         const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
75         if ((item_ptr->bi_key != BaseitemKey(ItemKindType::CORPSE, SV_CORPSE)) || (r_idx_of_item != MonsterRaceId::TSUCHINOKO)) {
76             continue;
77         }
78
79         change = true;
80         const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
81         if (!get_check(format(_("%s を換金しますか?", "Convert %s into money? "), item_name.data()))) {
82             continue;
83         }
84
85         msg_format(_("賞金 %ld$を手に入れた。", "You get %ldgp."), (long int)(200000L * item_ptr->number));
86         player_ptr->au += 200000L * item_ptr->number;
87         rfu.set_flag(MainWindowRedrawingFlag::GOLD);
88         vary_item(player_ptr, i, -item_ptr->number);
89     }
90
91     for (INVENTORY_IDX i = 0; i < INVEN_PACK; i++) {
92         const auto item_ptr = &player_ptr->inventory_list[i];
93         const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
94         if ((item_ptr->bi_key != BaseitemKey(ItemKindType::CORPSE, SV_SKELETON)) || (r_idx_of_item != MonsterRaceId::TSUCHINOKO)) {
95             continue;
96         }
97
98         change = true;
99         const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
100         if (!get_check(format(_("%s を換金しますか?", "Convert %s into money? "), item_name.data()))) {
101             continue;
102         }
103
104         msg_format(_("賞金 %ld$を手に入れた。", "You get %ldgp."), (long int)(100000L * item_ptr->number));
105         player_ptr->au += 100000L * item_ptr->number;
106         rfu.set_flag(MainWindowRedrawingFlag::GOLD);
107         vary_item(player_ptr, i, -item_ptr->number);
108     }
109
110     for (INVENTORY_IDX i = 0; i < INVEN_PACK; i++) {
111         const auto item_ptr = &player_ptr->inventory_list[i];
112         const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
113         if ((item_ptr->bi_key != BaseitemKey(ItemKindType::CORPSE, SV_CORPSE)) || (monraces_info[r_idx_of_item].name != monraces_info[w_ptr->today_mon].name)) {
114             continue;
115         }
116
117         change = true;
118         const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
119         if (!get_check(format(_("%s を換金しますか?", "Convert %s into money? "), item_name.data()))) {
120             continue;
121         }
122
123         constexpr auto mes = _("賞金 %ld$を手に入れた。", "You get %ldgp.");
124         msg_format(mes, (long int)((monraces_info[w_ptr->today_mon].level * 50 + 100) * item_ptr->number));
125         player_ptr->au += (monraces_info[w_ptr->today_mon].level * 50 + 100) * item_ptr->number;
126         rfu.set_flag(MainWindowRedrawingFlag::GOLD);
127         vary_item(player_ptr, i, -item_ptr->number);
128     }
129
130     for (INVENTORY_IDX i = 0; i < INVEN_PACK; i++) {
131         const auto item_ptr = &player_ptr->inventory_list[i];
132         const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
133         if ((item_ptr->bi_key != BaseitemKey(ItemKindType::CORPSE, SV_SKELETON)) || (monraces_info[r_idx_of_item].name != monraces_info[w_ptr->today_mon].name)) {
134             continue;
135         }
136
137         change = true;
138         const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
139         if (!get_check(format(_("%s を換金しますか?", "Convert %s into money? "), item_name.data()))) {
140             continue;
141         }
142
143         constexpr auto mes = _("賞金 %ld$を手に入れた。", "You get %ldgp.");
144         msg_format(mes, (long int)((monraces_info[w_ptr->today_mon].level * 30 + 60) * item_ptr->number));
145         player_ptr->au += (monraces_info[w_ptr->today_mon].level * 30 + 60) * item_ptr->number;
146         rfu.set_flag(MainWindowRedrawingFlag::GOLD);
147         vary_item(player_ptr, i, -item_ptr->number);
148     }
149
150     for (auto &[r_idx, is_achieved] : w_ptr->bounties) {
151         if (is_achieved) {
152             continue;
153         }
154
155         for (INVENTORY_IDX i = INVEN_PACK - 1; i >= 0; i--) {
156             const auto item_ptr = &player_ptr->inventory_list[i];
157             const auto r_idx_of_item = static_cast<MonsterRaceId>(item_ptr->pval);
158             if ((item_ptr->bi_key.tval() != ItemKindType::CORPSE) || (r_idx_of_item != r_idx)) {
159                 continue;
160             }
161
162             INVENTORY_IDX item_new;
163             ItemEntity forge;
164
165             const auto item_name = describe_flavor(player_ptr, item_ptr, 0);
166             if (!get_check(format(_("%sを渡しますか?", "Hand %s over? "), item_name.data()))) {
167                 continue;
168             }
169
170             vary_item(player_ptr, i, -item_ptr->number);
171             chg_virtue(player_ptr, Virtue::JUSTICE, 5);
172             is_achieved = true;
173
174             auto num = static_cast<int>(std::count_if(std::begin(w_ptr->bounties), std::end(w_ptr->bounties),
175                 [](const auto &b_ref) { return b_ref.is_achieved; }));
176
177             msg_print(_(format("これで合計 %d ポイント獲得しました。", num), format("You earned %d point%s total.", num, (num > 1 ? "s" : ""))));
178
179             (&forge)->prep(lookup_baseitem_id(prize_list[num - 1]));
180             ItemMagicApplier(player_ptr, &forge, player_ptr->current_floor_ptr->object_level, AM_NO_FIXED_ART).execute();
181
182             object_aware(player_ptr, &forge);
183             object_known(&forge);
184
185             /*
186              * Hand it --- Assume there is an empty slot.
187              * Since a corpse is handed at first,
188              * there is at least one empty slot.
189              */
190             item_new = store_item_to_inventory(player_ptr, &forge);
191             const auto got_item_name = describe_flavor(player_ptr, &forge, 0);
192             msg_format(_("%s(%c)を貰った。", "You get %s (%c). "), got_item_name.data(), index_to_label(item_new));
193
194             autopick_alter_item(player_ptr, item_new, false);
195             handle_stuff(player_ptr);
196             change = true;
197         }
198     }
199
200     if (change) {
201         return true;
202     }
203
204     msg_print(_("賞金を得られそうなものは持っていなかった。", "You have nothing."));
205     msg_print(nullptr);
206     return false;
207 }
208
209 /*!
210  * @brief 本日の賞金首情報を表示する。
211  * @param player_ptr プレイヤーへの参照ポインタ
212  */
213 void today_target(PlayerType *player_ptr)
214 {
215     auto *r_ptr = &monraces_info[w_ptr->today_mon];
216
217     clear_bldg(4, 18);
218     c_put_str(TERM_YELLOW, _("本日の賞金首", "Wanted monster that changes from day to day"), 5, 10);
219     c_put_str(TERM_YELLOW, format(_("ターゲット: %s", "target: %s"), r_ptr->name.data()), 6, 10);
220     prt(format(_("死体 ---- $%d", "corpse   ---- $%d"), (int)r_ptr->level * 50 + 100), 8, 10);
221     prt(format(_("骨   ---- $%d", "skeleton ---- $%d"), (int)r_ptr->level * 30 + 60), 9, 10);
222     player_ptr->knows_daily_bounty = true;
223 }
224
225 /*!
226  * @brief ツチノコの賞金首情報を表示する。
227  */
228 void tsuchinoko(void)
229 {
230     clear_bldg(4, 18);
231     c_put_str(TERM_YELLOW, _("一獲千金の大チャンス!!!", "Big chance for quick money!!!"), 5, 10);
232     c_put_str(TERM_YELLOW, _("ターゲット:幻の珍獣「ツチノコ」", "target: the rarest animal 'Tsuchinoko'"), 6, 10);
233     c_put_str(TERM_WHITE, _("生け捕り ---- $1,000,000", "catch alive ---- $1,000,000"), 8, 10);
234     c_put_str(TERM_WHITE, _("死体     ----   $200,000", "corpse      ----   $200,000"), 9, 10);
235     c_put_str(TERM_WHITE, _("骨       ----   $100,000", "bones       ----   $100,000"), 10, 10);
236 }
237
238 /*!
239  * @brief 通常の賞金首情報を表示する。
240  */
241 void show_bounty(void)
242 {
243     TERM_LEN y = 0;
244
245     clear_bldg(4, 18);
246     prt(_("死体を持ち帰れば報酬を差し上げます。", "Offer a prize when you bring a wanted monster's corpse"), 4, 10);
247     c_put_str(TERM_YELLOW, _("現在の賞金首", "Wanted monsters"), 6, 10);
248
249     for (auto i = 0U; i < std::size(w_ptr->bounties); i++) {
250         const auto &[r_idx, is_achieved] = w_ptr->bounties[i];
251         MonsterRaceInfo *r_ptr = &monraces_info[r_idx];
252
253         auto color = is_achieved ? TERM_RED : TERM_WHITE;
254         auto done_mark = is_achieved ? _("(済)", "(done)") : "";
255
256         c_prt(color, format("%s %s", r_ptr->name.data(), done_mark), y + 7, 10);
257
258         y = (y + 1) % 10;
259         if (!y && (i < std::size(w_ptr->bounties) - 1)) {
260             prt(_("何かキーを押してください", "Hit any key."), 0, 0);
261             (void)inkey();
262             prt("", 0, 0);
263             clear_bldg(7, 18);
264         }
265     }
266 }
267
268 /*!
269  * @brief 今日の賞金首を確定する / Determine today's bounty monster
270  * @param PlayerType プレイヤーへの参照ポインタ
271  * @note conv_old is used if loaded 0.0.3 or older save file
272  */
273 void determine_daily_bounty(PlayerType *player_ptr, bool conv_old)
274 {
275     int max_dl = 3;
276     if (!conv_old) {
277         for (const auto &d_ref : dungeons_info) {
278             if (max_dlv[d_ref.idx] < d_ref.mindepth) {
279                 continue;
280             }
281             if (max_dl < max_dlv[d_ref.idx]) {
282                 max_dl = max_dlv[d_ref.idx];
283             }
284         }
285     } else {
286         max_dl = std::max(max_dlv[DUNGEON_ANGBAND], 3);
287     }
288
289     get_mon_num_prep_bounty(player_ptr);
290
291     while (true) {
292         w_ptr->today_mon = get_mon_num(player_ptr, std::min(max_dl / 2, 40), max_dl, GMN_ARENA);
293         MonsterRaceInfo *r_ptr;
294         r_ptr = &monraces_info[w_ptr->today_mon];
295
296         if (cheat_hear) {
297             msg_format("日替わり候補: %s ", r_ptr->name.data());
298         }
299
300         if (r_ptr->kind_flags.has(MonsterKindType::UNIQUE)) {
301             continue;
302         }
303         if (r_ptr->population_flags.has(MonsterPopulationType::NAZGUL) || any_bits(r_ptr->flags7, RF7_UNIQUE2)) {
304             continue;
305         }
306         if (r_ptr->flags2 & RF2_MULTIPLY) {
307             continue;
308         }
309         if (!r_ptr->drop_flags.has_all_of({ MonsterDropType::DROP_CORPSE, MonsterDropType::DROP_SKELETON })) {
310             continue;
311         }
312         if (r_ptr->rarity > 10) {
313             continue;
314         }
315         break;
316     }
317
318     player_ptr->knows_daily_bounty = false;
319 }
320
321 /*!
322  * @brief 賞金首となるユニークを確定する / Determine bounty uniques
323  * @param player_ptr プレイヤーへの参照ポインタ
324  */
325 void determine_bounty_uniques(PlayerType *player_ptr)
326 {
327     get_mon_num_prep_bounty(player_ptr);
328
329     auto is_suitable_for_bounty = [](MonsterRaceId r_idx) {
330         const auto &r_ref = monraces_info[r_idx];
331         bool is_suitable = r_ref.kind_flags.has(MonsterKindType::UNIQUE);
332         is_suitable &= r_ref.drop_flags.has_any_of({ MonsterDropType::DROP_CORPSE, MonsterDropType::DROP_SKELETON });
333         is_suitable &= r_ref.rarity <= 100;
334         is_suitable &= !no_questor_or_bounty_uniques(r_idx);
335         return is_suitable;
336     };
337
338     // 賞金首とするモンスターの種族IDのリストを生成
339     std::vector<MonsterRaceId> bounty_r_idx_list;
340     while (bounty_r_idx_list.size() < std::size(w_ptr->bounties)) {
341         auto r_idx = get_mon_num(player_ptr, 0, MAX_DEPTH - 1, GMN_ARENA);
342         if (!is_suitable_for_bounty(r_idx)) {
343             continue;
344         }
345
346         auto is_already_selected = std::any_of(bounty_r_idx_list.begin(), bounty_r_idx_list.end(),
347             [r_idx](MonsterRaceId bounty_r_idx) { return r_idx == bounty_r_idx; });
348         if (!is_already_selected) {
349             bounty_r_idx_list.push_back(r_idx);
350         }
351     }
352
353     // モンスターのLVで昇順に並び替える
354     std::sort(bounty_r_idx_list.begin(), bounty_r_idx_list.end(),
355         [](MonsterRaceId r_idx1, MonsterRaceId r_idx2) {
356             return monraces_info[r_idx1].level < monraces_info[r_idx2].level;
357         });
358
359     // 賞金首情報を設定
360     std::transform(bounty_r_idx_list.begin(), bounty_r_idx_list.end(), std::begin(w_ptr->bounties),
361         [](MonsterRaceId r_idx) -> bounty_type {
362             return { r_idx, false };
363         });
364 }