OSDN Git Service

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