OSDN Git Service

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