OSDN Git Service

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