OSDN Git Service

Merge pull request #3838 from hengband/release/3.0.1.6-Beta
[hengbandforosx/hengbandosx.git] / src / smith / object-smith.cpp
1 #include "smith/object-smith.h"
2 #include "object-enchant/special-object-flags.h"
3 #include "object-enchant/tr-flags.h"
4 #include "object-enchant/tr-types.h"
5 #include "object/item-tester-hooker.h"
6 #include "perception/object-perception.h"
7 #include "player-base/player-class.h"
8 #include "player-info/smith-data-type.h"
9 #include "smith/smith-info.h"
10 #include "smith/smith-tables.h"
11 #include "smith/smith-types.h"
12 #include "system/item-entity.h"
13 #include "system/player-type-definition.h"
14 #include <algorithm>
15 #include <optional>
16 #include <sstream>
17 #include <unordered_map>
18 #include <vector>
19
20 namespace {
21
22 /**
23  * @brief 所持しているエッセンスで付与可能な回数を取得する
24  *
25  * @param essence_list 使用するエッセンスのリスト
26  * @param consumption 1種あたりに使用する消費量
27  * @return 所持しているエッセンスで付与可能な回数を返す
28  */
29 int addable_count(smith_data_type *smith_data, std::vector<SmithEssenceType> essence_list, int consumption)
30 {
31     if (consumption <= 0) {
32         return 0;
33     }
34
35     std::vector<int> addable_count;
36     for (auto essence : essence_list) {
37         int own_amount = smith_data->essences[essence];
38         addable_count.push_back(own_amount / consumption);
39     }
40     return *std::min_element(addable_count.begin(), addable_count.end());
41 }
42
43 }
44
45 /*!
46  * @brief 鍛冶クラスコンストラクタ
47  */
48 Smith::Smith(PlayerType *player_ptr)
49     : player_ptr(player_ptr)
50     , smith_data(PlayerClass(player_ptr).get_specific_data<smith_data_type>())
51 {
52 }
53
54 /*!
55  * @brief 引数で指定した鍛冶効果の鍛冶情報を得る
56  *
57  * @param effect 情報を得る鍛冶効果
58  * @return 鍛冶情報構造体へのポインタを保持する std::optional オブジェクトを返す
59  */
60 std::optional<const ISmithInfo *> Smith::find_smith_info(SmithEffectType effect)
61 {
62     // 何度も呼ぶので線形探索を避けるため鍛冶効果から鍛冶情報のテーブルを引けるmapを作成しておく。
63     static std::unordered_map<SmithEffectType, const ISmithInfo *> search_map;
64     if (search_map.empty()) {
65         for (const auto &info : smith_info_table) {
66             search_map.emplace(info->effect, info.get());
67         }
68     }
69
70     auto it = search_map.find(effect);
71     if (it == search_map.end()) {
72         return std::nullopt;
73     }
74
75     return it->second;
76 }
77
78 /*!
79  * @brief 指定したエッセンスの表記名を取得する
80  *
81  * @param essence 表記名を取得するエッセンス
82  * @return 表記名を表す文字列へのポインタ
83  */
84 concptr Smith::get_essence_name(SmithEssenceType essence)
85 {
86     auto it = essence_to_name.find(essence);
87     auto essence_name = it != essence_to_name.end() ? it->second : _("不明", "Unknown");
88     return essence_name;
89 }
90
91 /**
92  * @brief 指定した鍛冶効果の表記名を取得する
93  *
94  * @param effect 表記名を取得する鍛冶効果
95  * @return 表記名を表す文字列へのポインタ
96  */
97 concptr Smith::get_effect_name(SmithEffectType effect)
98 {
99     auto info = find_smith_info(effect);
100     return info ? (*info)->name : _("不明", "Unknown");
101 }
102
103 /*!
104  * @brief 指定した鍛冶効果の付与に必要なエッセンスの表記を取得する。複数のエッセンスが必要な場合は "+" で連結されたものとなる。
105  *
106  * @param effect 鍛冶効果
107  * @return 鍛冶効果の付与に必要なエッセンスを表記する std::string オブジェクト
108  */
109 std::string Smith::get_need_essences_desc(SmithEffectType effect)
110 {
111     auto info = find_smith_info(effect);
112     if (!info || (*info)->need_essences.empty()) {
113         return _("不明", "Unknown");
114     }
115
116     const auto &need_essences = (*info)->need_essences;
117     std::stringstream ss;
118     for (auto i = 0U; i < need_essences.size(); i++) {
119         ss << Smith::get_essence_name(need_essences[i]);
120         if (i < need_essences.size() - 1) {
121             ss << _("+", " + ");
122         }
123     }
124
125     return ss.str();
126 }
127
128 /*!
129  * @brief 鍛冶効果を付与するのに必要なエッセンスのリストを返す
130  *
131  * @param effect 鍛冶効果
132  * @return 鍛冶効果を付与するのに必要なエッセンスのリスト
133  */
134 std::vector<SmithEssenceType> Smith::get_need_essences(SmithEffectType effect)
135 {
136     auto info = find_smith_info(effect);
137     if (!info) {
138         return {};
139     }
140
141     return (*info)->need_essences;
142 }
143
144 /*!
145  * @brief 鍛冶効果を付与する時のエッセンス消費量を取得する
146  * 複数のエッセンスを消費する鍛冶効果の場合は全てのエッセンスからこの量が消費される
147  * アイテムが複数ある場合はアイテムの個数倍の消費量となる
148  *
149  * @param effect 鍛冶効果
150  * @param o_ptr 鍛冶効果を付与するアイテムへのポインタ。nullptrの場合はデフォルトの消費量が返される。
151  * @return 鍛冶効果を付与する時のエッセンス消費量
152  */
153 int Smith::get_essence_consumption(SmithEffectType effect, const ItemEntity *o_ptr)
154 {
155     auto info = find_smith_info(effect);
156     if (!info) {
157         return 0;
158     }
159
160     auto consumption = (*info)->consumption;
161     if (o_ptr == nullptr) {
162         return consumption;
163     }
164
165     if (o_ptr->is_ammo()) {
166         consumption = (consumption + 9) / 10;
167     }
168
169     consumption *= o_ptr->number;
170
171     return consumption;
172 }
173
174 /*!
175  * @brief 鍛冶効果の対象となるアイテムを絞り込むための ItemTester クラスを取得する
176  *
177  * @param effect 鍛冶効果
178  * @return ItemTesterクラスのオブジェクトをstd::unique_ptrに格納したものを返す
179  */
180 std::unique_ptr<ItemTester> Smith::get_item_tester(SmithEffectType effect)
181 {
182     auto info = find_smith_info(effect);
183     if (!info) {
184         return std::make_unique<TvalItemTester>(ItemKindType::NONE);
185     }
186
187     auto tester_func = [i = *info](const ItemEntity *o_ptr) {
188         return i->can_give_smith_effect(o_ptr);
189     };
190     return std::make_unique<FuncItemTester>(tester_func);
191 }
192
193 /*!
194  * @brief 鍛冶効果により得られる特性フラグを取得する
195  *
196  * @param effect 鍛冶効果
197  * @return 鍛冶効果により得られる特性フラグがONになったTrFlagsオブジェクト
198  */
199 TrFlags Smith::get_effect_tr_flags(SmithEffectType effect)
200 {
201     auto info = find_smith_info(effect);
202     if (!info) {
203         return {};
204     }
205
206     return (*info)->tr_flags();
207 }
208
209 /*!
210  * @brief アイテムに付与されている発動効果の発動IDを得る
211  *
212  * @param o_ptr アイテム構造体へのポインタ
213  * @return アイテムに付与されている発動効果の発動ID(random_art_activation_type型)
214  * 付与されている発動効果が無い場合は std::nullopt
215  */
216 std::optional<RandomArtActType> Smith::object_activation(const ItemEntity *o_ptr)
217 {
218     return o_ptr->smith_act_idx;
219 }
220
221 /*!
222  * @brief アイテムに付与されている鍛冶効果を取得する
223  *
224  * @param o_ptr アイテム構造体へのポインタ
225  * @return アイテムに付与されている鍛冶効果を保持する std::optional オブジェクト返す。
226  * 鍛冶効果が付与できないアイテムか、何も付与されていなければ std::nullopt を返す。
227  */
228 std::optional<SmithEffectType> Smith::object_effect(const ItemEntity *o_ptr)
229 {
230     return o_ptr->smith_effect;
231 }
232
233 /*!
234  * @brief 指定した鍛冶カテゴリの鍛冶効果のリストを取得する
235  *
236  * @param category 鍛冶カテゴリ
237  * @return 指定した鍛冶カテゴリの鍛冶効果のリスト
238  */
239 std::vector<SmithEffectType> Smith::get_effect_list(SmithCategoryType category)
240 {
241     std::vector<SmithEffectType> result;
242
243     for (const auto &info : smith_info_table) {
244         if (info->category == category) {
245             result.push_back(info->effect);
246         }
247     }
248
249     return result;
250 }
251
252 /**
253  * @brief 指定した鍛冶効果のエッセンスを付与できる回数を取得する
254  *
255  * @param effect 鍛冶効果
256  * @param o_ptr 鍛冶効果を付与するアイテムへのポインタ。nullptrの場合はデフォルトの消費量での回数が返される。
257  * @return エッセンスを付与できる回数を返す
258  */
259 int Smith::get_addable_count(SmithEffectType effect, const ItemEntity *o_ptr) const
260 {
261     auto info = find_smith_info(effect);
262     if (!info) {
263         return 0;
264     }
265
266     auto consumption = Smith::get_essence_consumption(effect, o_ptr);
267
268     return addable_count(this->smith_data.get(), (*info)->need_essences, consumption);
269 }
270
271 /*!
272  * @brief 鍛冶エッセンスの全リストを取得する
273  *
274  * @return 鍛冶エッセンスの全リスト
275  */
276 const std::vector<SmithEssenceType> &Smith::get_essence_list()
277 {
278     return essence_list_order;
279 }
280
281 /*!
282  * @brief 指定したエッセンスの所持量を取得する
283  *
284  * @param essence 所持量を取得するエッセンス
285  * @return エッセンスの所持量
286  */
287 int Smith::get_essence_num_of_posessions(SmithEssenceType essence) const
288 {
289     return this->smith_data->essences[essence];
290 }
291
292 /*!
293  * @brief エッセンスの抽出を行う
294  *
295  * @param o_ptr エッセンスの抽出を行うアイテムへのポインタ
296  * @return 抽出したエッセンスと抽出した量のタプルのリストを返す
297  */
298 Smith::DrainEssenceResult Smith::drain_essence(ItemEntity *o_ptr)
299 {
300     // 抽出量を揃えるためKILLフラグのみ付いている場合はSLAYフラグも付ける
301     auto old_flags = o_ptr->get_flags();
302     if (old_flags.has(TR_KILL_DRAGON)) {
303         old_flags.set(TR_SLAY_DRAGON);
304     }
305     if (old_flags.has(TR_KILL_ANIMAL)) {
306         old_flags.set(TR_SLAY_ANIMAL);
307     }
308     if (old_flags.has(TR_KILL_EVIL)) {
309         old_flags.set(TR_SLAY_EVIL);
310     }
311     if (old_flags.has(TR_KILL_UNDEAD)) {
312         old_flags.set(TR_SLAY_UNDEAD);
313     }
314     if (old_flags.has(TR_KILL_DEMON)) {
315         old_flags.set(TR_SLAY_DEMON);
316     }
317     if (old_flags.has(TR_KILL_ORC)) {
318         old_flags.set(TR_SLAY_ORC);
319     }
320     if (old_flags.has(TR_KILL_TROLL)) {
321         old_flags.set(TR_SLAY_TROLL);
322     }
323     if (old_flags.has(TR_KILL_GIANT)) {
324         old_flags.set(TR_SLAY_GIANT);
325     }
326     if (old_flags.has(TR_KILL_HUMAN)) {
327         old_flags.set(TR_SLAY_HUMAN);
328     }
329     if (old_flags.has(TR_KILL_GOOD)) {
330         old_flags.set(TR_SLAY_GOOD);
331     }
332
333     // マイナス効果のあるアイテムから抽出する時のペナルティを計算
334     auto dec = 4;
335     if (o_ptr->curse_flags.has_any_of({ CurseTraitType::CURSED, CurseTraitType::HEAVY_CURSE, CurseTraitType::PERMA_CURSE })) {
336         dec--;
337     }
338
339     for (auto &&info : essence_drain_info_table) {
340         if (info.amount < 0 && old_flags.has(info.tr_flag)) {
341             dec += info.amount;
342         }
343     }
344
345     const auto is_fixed_or_random_artifact = o_ptr->is_fixed_or_random_artifact();
346
347     // アイテムをエッセンス抽出後の状態にする
348     const ItemEntity old_o = *o_ptr;
349     o_ptr->prep(o_ptr->bi_id);
350     o_ptr->iy = old_o.iy;
351     o_ptr->ix = old_o.ix;
352     o_ptr->marked = old_o.marked;
353     o_ptr->number = old_o.number;
354     o_ptr->discount = old_o.discount;
355     if (o_ptr->bi_key.tval() == ItemKindType::DRAG_ARMOR) {
356         o_ptr->timeout = old_o.timeout;
357     }
358
359     o_ptr->ident |= (IDENT_FULL_KNOWN);
360     object_aware(player_ptr, o_ptr);
361     o_ptr->mark_as_known();
362
363     const auto new_flags = o_ptr->get_flags();
364
365     std::unordered_map<SmithEssenceType, int> drain_values;
366
367     // 特性フラグからのエッセンス抽出
368     for (auto &&info : essence_drain_info_table) {
369         int pval = 0;
370         if (TR_PVAL_FLAG_MASK.has(info.tr_flag) && old_o.pval > 0) {
371             pval = new_flags.has(info.tr_flag) ? old_o.pval - o_ptr->pval : old_o.pval;
372         }
373
374         if ((new_flags.has_not(info.tr_flag) || pval) && old_flags.has(info.tr_flag)) {
375             for (auto &&essence : info.essences) {
376                 auto mult = TR_PVAL_FLAG_MASK.has(info.tr_flag) ? pval : 1;
377                 drain_values[essence] += info.amount * mult;
378             }
379         }
380     }
381
382     if (is_fixed_or_random_artifact) {
383         drain_values[SmithEssenceType::UNIQUE] += 10;
384     }
385
386     // ダイス/命中/ダメージ/ACからの抽出
387     auto diff = [](int o, int n) { return std::max(o - n, 0); };
388
389     if (o_ptr->is_weapon_ammo()) {
390         drain_values[SmithEssenceType::ATTACK] += diff(old_o.ds, o_ptr->ds) * 10;
391         drain_values[SmithEssenceType::ATTACK] += diff(old_o.dd, o_ptr->dd) * 10;
392     }
393
394     drain_values[SmithEssenceType::ATTACK] += diff(old_o.to_h, o_ptr->to_h) * 10;
395     drain_values[SmithEssenceType::ATTACK] += diff(old_o.to_d, o_ptr->to_d) * 10;
396     drain_values[SmithEssenceType::AC] += diff(old_o.ac, o_ptr->ac) * 10;
397     drain_values[SmithEssenceType::AC] += diff(old_o.to_a, o_ptr->to_a) * 10;
398
399     // 個数/矢弾/マイナス効果のペナルティによる抽出量の調整
400     for (auto &&[unuse, val] : drain_values) {
401         val *= o_ptr->number;
402         val = val * dec / 4;
403         val = std::max(val, 0);
404         if (o_ptr->is_ammo()) {
405             val /= 10;
406         }
407     }
408
409     // 所持エッセンスに追加
410     std::vector<std::tuple<SmithEssenceType, int>> result;
411
412     for (auto essence : essence_list_order) {
413         auto drain_value = drain_values[essence];
414         if (drain_value <= 0) {
415             continue;
416         }
417
418         this->smith_data->essences[essence] = std::min<int16_t>(Smith::ESSENCE_AMOUNT_MAX, this->smith_data->essences[essence] + drain_value);
419         result.emplace_back(essence, drain_value);
420     }
421
422     return result;
423 }
424
425 /*!
426  * @brief 鍛冶効果を付与する
427  *
428  * @param effect 付与する鍛冶効果
429  * @param o_ptr 鍛冶効果を付与するアイテムへのポインタ
430  * @param number エッセンス付与数
431  * @return 鍛冶効果の付与に成功したら ture、失敗したら false を返す
432  */
433 bool Smith::add_essence(SmithEffectType effect, ItemEntity *o_ptr, int number)
434 {
435     auto info = find_smith_info(effect);
436     if (!info) {
437         return false;
438     }
439
440     const auto total_consumption = this->get_essence_consumption(effect, o_ptr) * number;
441     for (auto &&essence : (*info)->need_essences) {
442         this->smith_data->essences[essence] -= static_cast<int16_t>(total_consumption);
443     }
444
445     return (*info)->add_essence(this->player_ptr, o_ptr, number);
446 }
447
448 /*!
449  * @brief 鍛冶効果を消去する
450  *
451  * @param o_ptr 鍛冶効果を消去するアイテムへのポインタ
452  */
453 void Smith::erase_essence(ItemEntity *o_ptr) const
454 {
455     o_ptr->smith_act_idx = std::nullopt;
456
457     auto effect = Smith::object_effect(o_ptr);
458     if (!effect) {
459         return;
460     }
461     auto info = find_smith_info(*effect);
462     if (!info) {
463         return;
464     }
465
466     (*info)->erase_essence(o_ptr);
467 }