OSDN Git Service

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