OSDN Git Service

[Refactor] #3217 baseitems_info への直接アクセスを、ItemEntity::get_baseitem() に差し替えた
[hengbandforosx/hengbandosx.git] / src / flavor / named-item-describer.cpp
1 #include "flavor/named-item-describer.h"
2 #include "artifact/fixed-art-types.h"
3 #include "flavor/flavor-util.h"
4 #include "flavor/object-flavor-types.h"
5 #include "flavor/tval-description-switcher.h"
6 #include "game-option/text-display-options.h"
7 #include "locale/english.h"
8 #include "mind/mind-weaponsmith.h"
9 #include "object-enchant/object-ego.h"
10 #include "object-enchant/special-object-flags.h"
11 #include "object-enchant/tr-types.h"
12 #include "object/object-flags.h"
13 #include "perception/object-perception.h"
14 #include "system/artifact-type-definition.h"
15 #include "system/item-entity.h"
16 #include "system/player-type-definition.h"
17 #include "util/bit-flags-calculator.h"
18 #include "util/quarks.h"
19 #include "util/string-processor.h"
20 #ifdef JP
21 #else
22 #include "monster-race/monster-race.h"
23 #include "monster-race/race-flags1.h"
24 #include "object/tval-types.h"
25 #include "system/monster-race-info.h"
26 #endif
27 #include <sstream>
28
29 static std::string get_fullname_if_set(const ItemEntity &item, const describe_option_type &opt)
30 {
31     if (!opt.aware || object_flags(&item).has_not(TR_FULL_NAME)) {
32         return "";
33     }
34
35     const auto fixed_art_id = item.fixed_artifact_idx;
36     const auto is_known_artifact = opt.known && item.is_fixed_artifact() && none_bits(opt.mode, OD_BASE_NAME);
37     return is_known_artifact ? ArtifactsInfo::get_instance().get_artifact(fixed_art_id).name : item.get_baseitem().name;
38 }
39
40 #ifdef JP
41 /*!
42  * @brief アイテムの個数を記述する
43  *
44  * @param item アイテムへの参照
45  * @param opt 記述オプション
46  * @return アイテムの個数を記述した文字列
47  */
48 static std::string describe_item_count_ja(const ItemEntity &item, const describe_option_type &opt)
49 {
50     if (any_bits(opt.mode, OD_OMIT_PREFIX) || (item.number <= 1)) {
51         return "";
52     }
53
54     return describe_count_with_counter_suffix(item) + "の ";
55 }
56
57 /*!
58  * @brief アーティファクトであるマークを記述する
59  *
60  * 英語の場合アーティファクトは The が付くので分かるが、日本語では分からないので
61  * 固定アーティファクトなら「★」、ランダムアーティファクトなら「☆」マークをつける.
62  *
63  * @param item アイテムへの参照
64  * @param opt 記述オプション
65  */
66 static std::string describe_artifact_mark_ja(const ItemEntity &item, const describe_option_type &opt)
67 {
68     if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
69         return "";
70     }
71
72     if (item.is_fixed_artifact()) {
73         return "★";
74     } else if (item.is_random_artifact()) {
75         return "☆";
76     }
77
78     return "";
79 }
80
81 /*!
82  * @brief アイテムの固有名称(アイテム本体の手前に表記するもの)を記述する
83  *
84  * 例)
85  * 固定/ランダムアーティファクト: ★リリアのダガー → "リリアの"
86  * エゴアイテム: (聖戦者)ロング・ソード → "(聖戦者)"
87  *
88  * @param item アイテムへの参照
89  * @param opt 記述オプション
90  * @return 記述した文字列
91  */
92 static std::string describe_unique_name_before_body_ja(const ItemEntity &item, const describe_option_type &opt)
93 {
94     if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
95         return "";
96     }
97
98     if (item.is_random_artifact()) {
99         const std::string_view name_sv = item.randart_name.value();
100
101         /* '『' から始まらない伝説のアイテムの名前は最初に付加する */
102         /* 英語版のセーブファイルから来た 'of XXX' は,「XXXの」と表示する */
103         if (name_sv.starts_with("of ")) {
104             std::stringstream ss;
105             ss << name_sv.substr(3) << "の";
106             return ss.str();
107         } else if (!name_sv.starts_with("『") && !name_sv.starts_with("《") && !name_sv.starts_with('\'')) {
108             return item.randart_name.value();
109         }
110     }
111
112     if (item.is_fixed_artifact() && object_flags(&item).has_not(TR_FULL_NAME)) {
113         const auto &artifact = ArtifactsInfo::get_instance().get_artifact(item.fixed_artifact_idx);
114         /* '『' から始まらない伝説のアイテムの名前は最初に付加する */
115         if (artifact.name.find("『", 0, 2) != 0) {
116             return artifact.name;
117         }
118
119         return "";
120     }
121
122     if (item.is_ego()) {
123         return item.get_ego().name;
124     }
125
126     return "";
127 }
128
129 static std::optional<std::string> describe_random_artifact_name_after_body_ja(const ItemEntity &item)
130 {
131     if (!item.is_random_artifact()) {
132         return std::nullopt;
133     }
134
135     const std::string_view name_sv = item.randart_name.value();
136     if (name_sv.starts_with("『") || name_sv.starts_with("《")) {
137         return item.randart_name;
138     }
139
140     if (!name_sv.starts_with('\'')) {
141         return "";
142     }
143
144     // "'foobar'" の foobar の部分を取り出し『foobar』と表記する
145     // (英語版のセーブファイルのランダムアーティファクトを考慮)
146     return format("『%s』", name_sv.substr(1, name_sv.length() - 2).data());
147 }
148
149 static std::string describe_fake_artifact_name_after_body_ja(const ItemEntity &item)
150 {
151     if (!item.is_inscribed()) {
152         return "";
153     }
154
155     auto str = item.inscription->data();
156     while (*str) {
157         if (iskanji(*str)) {
158             str += 2;
159             continue;
160         }
161
162         if (*str == '#') {
163             break;
164         }
165
166         str++;
167     }
168
169     if (*str == '\0') {
170         return "";
171     }
172
173     auto str_aux = angband_strchr(item.inscription->data(), '#');
174     return format("『%s』", str_aux + 1);
175 }
176
177 /*!
178  * @brief アイテムの固有名称(アイテム本体の後に表記するもの)を記述する。
179  *
180  * 例)
181  * 固定/ランダムアーティファクト: ★ロング・ソード『リンギル』 → "『リンギル』"
182  * 銘刻みによる疑似アーティファクト: ロング・ソード『AIR』 → "『AIR』"
183  * エゴアイテムはアイテム本体の後に記述されるタイプのものは存在しない。
184  *
185  * 銘刻みによる疑似アーティファクトは、銘の最後に #foobar と刻むことにより
186  * ゲーム上の表記が「アイテム名『foobar』」のようになる機能。
187  *
188  * @param item アイテムへの参照
189  * @param opt 記述オプション
190  * @return 記述した文字列
191  */
192 static std::string describe_unique_name_after_body_ja(const ItemEntity &item, const describe_option_type &opt)
193 {
194     if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
195         return "";
196     }
197
198     if (auto body = describe_random_artifact_name_after_body_ja(item); body.has_value()) {
199         return body.value();
200     }
201
202     if (item.is_fixed_artifact()) {
203         const auto &artifact = ArtifactsInfo::get_instance().get_artifact(item.fixed_artifact_idx);
204         if (artifact.name.find("『", 0, 2) == 0) {
205             return artifact.name;
206         }
207
208         return "";
209     }
210
211     return describe_fake_artifact_name_after_body_ja(item);
212 }
213 #else
214
215 static std::string describe_vowel(const ItemEntity &item, std::string_view basename, std::string_view modstr)
216 {
217     bool vowel;
218     switch (basename[0]) {
219     case '#':
220         vowel = is_a_vowel(modstr[0]);
221         break;
222     case '%':
223         vowel = is_a_vowel(baseitems_info[item.bi_id].name[0]);
224         break;
225     default:
226         vowel = is_a_vowel(basename[0]);
227         break;
228     }
229
230     return (vowel) ? "an " : "a ";
231 }
232
233 static std::string describe_prefix_en(const ItemEntity &item)
234 {
235     if (item.number <= 0) {
236         return "no more ";
237     }
238
239     if (item.number == 1) {
240         return "";
241     }
242
243     return std::to_string(item.number) + ' ';
244 }
245
246 static std::string describe_item_count_or_article_en(const ItemEntity &item, const describe_option_type &opt, std::string_view basename, std::string_view modstr)
247 {
248     if (any_bits(opt.mode, OD_OMIT_PREFIX)) {
249         return "";
250     }
251
252     if (auto prefix = describe_prefix_en(item); !prefix.empty()) {
253         return prefix;
254     }
255
256     const auto corpse_r_idx = i2enum<MonsterRaceId>(item.pval);
257     auto is_unique_corpse = item.bi_key.tval() == ItemKindType::CORPSE;
258     is_unique_corpse &= monraces_info[corpse_r_idx].kind_flags.has(MonsterKindType::UNIQUE);
259     if ((opt.known && item.is_fixed_or_random_artifact()) || is_unique_corpse) {
260         return "The ";
261     }
262
263     return describe_vowel(item, basename, modstr);
264 }
265
266 static std::string describe_item_count_or_definite_article_en(const ItemEntity &item, const describe_option_type &opt)
267 {
268     if (any_bits(opt.mode, OD_OMIT_PREFIX)) {
269         return "";
270     }
271
272     if (auto prefix = describe_prefix_en(item); !prefix.empty()) {
273         return prefix;
274     }
275
276     if (opt.known && item.is_fixed_or_random_artifact()) {
277         return "The ";
278     }
279
280     return "";
281 }
282
283 static std::string describe_unique_name_after_body_en(const ItemEntity &item, const describe_option_type &opt)
284 {
285     if (!opt.known || object_flags(&item).has(TR_FULL_NAME) || any_bits(opt.mode, OD_BASE_NAME)) {
286         return "";
287     }
288
289     std::stringstream ss;
290
291     if (item.is_random_artifact()) {
292         ss << ' ' << item.randart_name.value();
293         return ss.str();
294     }
295
296     if (item.is_fixed_artifact()) {
297         const auto &artifact = ArtifactsInfo::get_instance().get_artifact(item.fixed_artifact_idx);
298         ss << ' ' << artifact.name;
299         return ss.str();
300     }
301
302     if (item.is_ego()) {
303         ss << ' ' << egos_info[item.ego_idx].name;
304     }
305
306     if (item.is_inscribed()) {
307         if (auto str = angband_strchr(item.inscription->data(), '#'); str != nullptr) {
308             ss << ' ' << str + 1;
309         }
310     }
311
312     return ss.str();
313 }
314 #endif
315
316 /*!
317  * @brief アイテム本体の名称を記述する
318  *
319  * 基本的には basename の内容がそのまま記述されるが、basename 上の特定の文字に対して以下の修正が行われる。
320  *
321  * - '#' が modstr の内容で置き換えられる:
322  *   主に未鑑定名やモンスターボール・像・死体などのモンスター名の部分に使用される
323  * - '%' が item のベースアイテム名で置き換えられる:
324  *   BaseitemDefinition.txt でカテゴリ内における名称のみが記述されているものに使用される。
325  *   たとえば薬の場合 basename は「%の薬」となっており、BaseitemDefinition.txt 内で記述されている
326  *   "体力回復" により '%' が置き換えられ「体力回復の薬」と表記されるといった具合。
327  * - '~' がアイテムが複数の場合複数形表現で置き換えられる(英語版のみ)
328  *
329  * @param item アイテムへの参照
330  * @param opt 記述オプション
331  * @param basename アイテム本体のベースとなる文字列
332  * @param modstr ベースとなる文字列上の特定の文字を置き換える文字列
333  * @return 記述した文字列
334  */
335 static std::string describe_body(const ItemEntity &item, [[maybe_unused]] const describe_option_type &opt, std::string_view basename, std::string_view modstr)
336 {
337 #ifndef JP
338     auto pluralize = [&opt, &item](auto &ss, auto it) {
339         if (none_bits(opt.mode, OD_NO_PLURAL) && (item.number != 1)) {
340             char k = *std::next(it, -1);
341             if ((k == 's') || (k == 'h')) {
342                 ss << 'e';
343             }
344             ss << 's';
345         }
346     };
347 #endif
348     std::stringstream ss;
349
350     for (auto it = basename.begin(), it_end = basename.end(); it != it_end; ++it) {
351         switch (*it) {
352         case '#':
353             ss << modstr;
354             break;
355
356         case '%': {
357             const auto &baseitem = item.get_baseitem();
358 #ifdef JP
359             ss << baseitem.name;
360 #else
361             for (auto ib = baseitem.name.begin(), ib_end = baseitem.name.end(); ib != ib_end; ++ib) {
362                 if (*ib == '~') {
363                     pluralize(ss, ib);
364                 } else {
365                     ss << *ib;
366                 }
367             }
368 #endif
369             break;
370         }
371
372 #ifndef JP
373         case '~':
374             pluralize(ss, it);
375             break;
376 #endif
377
378         default:
379             ss << *it;
380 #ifdef JP
381             if (iskanji(*it)) {
382                 ++it;
383                 ss << *it;
384             }
385 #endif
386             break;
387         }
388     }
389
390     return ss.str();
391 }
392
393 /*!
394  * @brief アイテム名を記述する
395  *
396  * @param item アイテムへの参照
397  * @param opt 記述オプション
398  * @return std::string 記述したアイテム名
399  */
400 std::string describe_named_item(PlayerType *player_ptr, const ItemEntity &item, const describe_option_type &opt)
401 {
402     auto [basename, modstr] = switch_tval_description(item, opt);
403     if (auto name = get_fullname_if_set(item, opt); !name.empty()) {
404         basename = std::move(name);
405     }
406     std::string_view basename_sv = basename;
407     std::stringstream ss;
408
409 #ifdef JP
410     if (basename_sv[0] == '&') {
411         basename_sv.remove_prefix(2);
412     }
413     ss << describe_item_count_ja(item, opt)
414        << describe_artifact_mark_ja(item, opt);
415 #else
416     if (basename_sv[0] == '&') {
417         basename_sv.remove_prefix(2);
418         ss << describe_item_count_or_article_en(item, opt, basename_sv, modstr);
419     } else {
420         ss << describe_item_count_or_definite_article_en(item, opt);
421     }
422 #endif
423
424 #ifdef JP
425     if (item.is_smith() && none_bits(opt.mode, OD_BASE_NAME)) {
426         ss << format("鍛冶師%sの", player_ptr->name);
427     }
428
429     ss << describe_unique_name_before_body_ja(item, opt);
430 #endif
431     if (item.is_spell_book()) {
432         // svalは0から数えているので表示用に+1している
433         ss << format("Lv%d ", item.bi_key.sval().value() + 1);
434     }
435
436     ss << describe_body(item, opt, basename_sv, modstr);
437
438 #ifdef JP
439     ss << describe_unique_name_after_body_ja(item, opt);
440 #else
441     if (item.is_smith() && none_bits(opt.mode, OD_BASE_NAME)) {
442         ss << format(" of %s the Smith", player_ptr->name);
443     }
444
445     ss << describe_unique_name_after_body_en(item, opt);
446 #endif
447
448     return ss.str();
449 }