OSDN Git Service

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