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"
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"
27 static std::string get_fullname_if_set(const ItemEntity &item, const describe_option_type &opt)
29 if (!opt.aware || item.get_flags().has_not(TR_FULL_NAME)) {
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;
41 * @param item アイテムへの参照
43 * @return アイテムの個数を記述した文字列
45 static std::string describe_item_count_ja(const ItemEntity &item, const describe_option_type &opt)
47 if (any_bits(opt.mode, OD_OMIT_PREFIX) || (item.number <= 1)) {
51 return describe_count_with_counter_suffix(item) + "の ";
55 * @brief アーティファクトであるマークを記述する
57 * 英語の場合アーティファクトは The が付くので分かるが、日本語では分からないので
58 * 固定アーティファクトなら「★」、ランダムアーティファクトなら「☆」マークをつける.
60 * @param item アイテムへの参照
63 static std::string describe_artifact_mark_ja(const ItemEntity &item, const describe_option_type &opt)
65 if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
69 if (item.is_fixed_artifact()) {
71 } else if (item.is_random_artifact()) {
79 * @brief アイテムの固有名称(アイテム本体の手前に表記するもの)を記述する
82 * 固定/ランダムアーティファクト: ★リリアのダガー → "リリアの"
83 * エゴアイテム: (聖戦者)ロング・ソード → "(聖戦者)"
85 * @param item アイテムへの参照
89 static std::string describe_unique_name_before_body_ja(const ItemEntity &item, const describe_option_type &opt)
91 if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
95 if (item.is_random_artifact()) {
96 const std::string_view name_sv = item.randart_name.value();
98 /* '『' から始まらない伝説のアイテムの名前は最初に付加する */
99 /* 英語版のセーブファイルから来た 'of XXX' は,「XXXの」と表示する */
100 if (name_sv.starts_with("of ")) {
101 std::stringstream ss;
102 ss << name_sv.substr(3) << "の";
104 } else if (!name_sv.starts_with("『") && !name_sv.starts_with("《") && !name_sv.starts_with('\'')) {
105 return item.randart_name.value();
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;
120 return item.get_ego().name;
126 static std::optional<std::string> describe_random_artifact_name_after_body_ja(const ItemEntity &item)
128 if (!item.is_random_artifact()) {
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;
137 if (!name_sv.starts_with('\'')) {
141 // "'foobar'" の foobar の部分を取り出し『foobar』と表記する
142 // (英語版のセーブファイルのランダムアーティファクトを考慮)
143 return format("『%s』", name_sv.substr(1, name_sv.length() - 2).data());
146 static std::string describe_fake_artifact_name_after_body_ja(const ItemEntity &item)
148 if (!item.is_inscribed()) {
152 auto str = item.inscription->data();
170 auto str_aux = angband_strchr(item.inscription->data(), '#');
171 return format("『%s』", str_aux + 1);
175 * @brief アイテムの固有名称(アイテム本体の後に表記するもの)を記述する。
178 * 固定/ランダムアーティファクト: ★ロング・ソード『リンギル』 → "『リンギル』"
179 * 銘刻みによる疑似アーティファクト: ロング・ソード『AIR』 → "『AIR』"
180 * エゴアイテムはアイテム本体の後に記述されるタイプのものは存在しない。
182 * 銘刻みによる疑似アーティファクトは、銘の最後に #foobar と刻むことにより
183 * ゲーム上の表記が「アイテム名『foobar』」のようになる機能。
185 * @param item アイテムへの参照
189 static std::string describe_unique_name_after_body_ja(const ItemEntity &item, const describe_option_type &opt)
191 if (!opt.known || any_bits(opt.mode, OD_BASE_NAME)) {
195 if (auto body = describe_random_artifact_name_after_body_ja(item); body) {
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;
208 return describe_fake_artifact_name_after_body_ja(item);
212 static std::string describe_vowel(const ItemEntity &item, std::string_view basename, std::string_view modstr)
215 switch (basename[0]) {
217 vowel = is_a_vowel(modstr[0]);
220 vowel = is_a_vowel(baseitems_info[item.bi_id].name[0]);
223 vowel = is_a_vowel(basename[0]);
227 return (vowel) ? "an " : "a ";
230 static std::string describe_prefix_en(const ItemEntity &item)
232 if (item.number <= 0) {
236 if (item.number == 1) {
240 return std::to_string(item.number) + ' ';
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)
245 if (any_bits(opt.mode, OD_OMIT_PREFIX)) {
249 if (auto prefix = describe_prefix_en(item); !prefix.empty()) {
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) {
260 return describe_vowel(item, basename, modstr);
263 static std::string describe_item_count_or_definite_article_en(const ItemEntity &item, const describe_option_type &opt)
265 if (any_bits(opt.mode, OD_OMIT_PREFIX)) {
269 if (auto prefix = describe_prefix_en(item); !prefix.empty()) {
273 if (opt.known && item.is_fixed_or_random_artifact()) {
280 static std::string describe_unique_name_after_body_en(const ItemEntity &item, const describe_option_type &opt)
282 if (!opt.known || item.get_flags().has(TR_FULL_NAME) || any_bits(opt.mode, OD_BASE_NAME)) {
286 std::stringstream ss;
288 if (item.is_random_artifact()) {
289 ss << ' ' << item.randart_name.value();
293 if (item.is_fixed_artifact()) {
294 const auto &artifact = ArtifactsInfo::get_instance().get_artifact(item.fixed_artifact_idx);
295 ss << ' ' << artifact.name;
300 ss << ' ' << egos_info[item.ego_idx].name;
303 if (item.is_inscribed()) {
304 if (auto str = angband_strchr(item.inscription->data(), '#'); str != nullptr) {
305 ss << ' ' << str + 1;
314 * @brief アイテム本体の名称を記述する
316 * 基本的には basename の内容がそのまま記述されるが、basename 上の特定の文字に対して以下の修正が行われる。
318 * - '#' が modstr の内容で置き換えられる:
319 * 主に未鑑定名やモンスターボール・像・死体などのモンスター名の部分に使用される
320 * - '%' が item のベースアイテム名で置き換えられる:
321 * BaseitemDefinition.txt でカテゴリ内における名称のみが記述されているものに使用される。
322 * たとえば薬の場合 basename は「%の薬」となっており、BaseitemDefinition.txt 内で記述されている
323 * "体力回復" により '%' が置き換えられ「体力回復の薬」と表記されるといった具合。
324 * - '~' がアイテムが複数の場合複数形表現で置き換えられる(英語版のみ)
326 * @param item アイテムへの参照
328 * @param basename アイテム本体のベースとなる文字列
329 * @param modstr ベースとなる文字列上の特定の文字を置き換える文字列
332 static std::string describe_body(const ItemEntity &item, [[maybe_unused]] const describe_option_type &opt, std::string_view basename, std::string_view modstr)
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')) {
345 std::stringstream ss;
347 for (auto it = basename.begin(), it_end = basename.end(); it != it_end; ++it) {
354 const auto &baseitem = item.get_baseitem();
358 for (auto ib = baseitem.name.begin(), ib_end = baseitem.name.end(); ib != ib_end; ++ib) {
393 * @param item アイテムへの参照
395 * @return std::string 記述したアイテム名
397 std::string describe_named_item(PlayerType *player_ptr, const ItemEntity &item, const describe_option_type &opt)
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);
403 std::string_view basename_sv = basename;
404 std::stringstream ss;
407 if (basename_sv[0] == '&') {
408 basename_sv.remove_prefix(2);
410 ss << describe_item_count_ja(item, opt)
411 << describe_artifact_mark_ja(item, opt);
413 if (basename_sv[0] == '&') {
414 basename_sv.remove_prefix(2);
415 ss << describe_item_count_or_article_en(item, opt, basename_sv, modstr);
417 ss << describe_item_count_or_definite_article_en(item, opt);
422 if (item.is_smith() && none_bits(opt.mode, OD_BASE_NAME)) {
423 ss << format("鍛冶師%sの", player_ptr->name);
426 ss << describe_unique_name_before_body_ja(item, opt);
428 if (item.is_spell_book()) {
429 // svalは0から数えているので表示用に+1している
430 ss << format("Lv%d ", *item.bi_key.sval() + 1);
433 ss << describe_body(item, opt, basename_sv, modstr);
436 ss << describe_unique_name_after_body_ja(item, opt);
438 if (item.is_smith() && none_bits(opt.mode, OD_BASE_NAME)) {
439 ss << format(" of %s the Smith", player_ptr->name);
442 ss << describe_unique_name_after_body_en(item, opt);