OSDN Git Service

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