OSDN Git Service

Merge pull request #3297 from Hourier/Move-Update-Flags-3
[hengbandforosx/hengbandosx.git] / src / store / rumor.cpp
1 #include "store/rumor.h"
2 #include "flavor/flavor-describer.h"
3 #include "flavor/object-flavor-types.h"
4 #include "floor/floor-town.h"
5 #include "io/files-util.h"
6 #include "io/tokenizer.h"
7 #include "monster-race/monster-race.h"
8 #include "object-enchant/special-object-flags.h"
9 #include "object/object-kind-hook.h"
10 #include "system/artifact-type-definition.h"
11 #include "system/baseitem-info.h"
12 #include "system/dungeon-info.h"
13 #include "system/item-entity.h"
14 #include "system/monster-race-info.h"
15 #include "system/player-type-definition.h"
16 #include "view/display-messages.h"
17 #include "world/world.h"
18 #include <algorithm>
19 #include <sstream>
20 #include <stdexcept>
21 #include <string>
22 #include <string_view>
23 #include <utility>
24
25 /*
26  * @brief 固定アーティファクト、モンスター、町 をランダムに1つ選び、ダンジョンを固定的に1つ選ぶ
27  * @param zz 検索文字列
28  * @param max_idx briefに挙げた各リストにおける最大数
29  * @details rumor.txt (rumor_j.txt) の定義により、ダンジョンは鉄獄 (ダンジョンID1)が常に選ばれる
30  * その他は常にランダム ("*")
31  */
32 static short get_rumor_num(std::string_view zz, short max_idx)
33 {
34     if (zz == "*") {
35         return randint1(max_idx);
36     }
37
38     return static_cast<short>(atoi(zz.data()));
39 }
40
41 static std::string bind_rumor_name(std::string_view base, std::string_view item_name)
42 {
43     if (const auto pos = base.find("{Name}");
44         pos != std::string::npos) {
45         const auto head = base.substr(0, pos);
46         const auto tail = base.substr(pos + 6);
47         std::stringstream ss;
48         ss << head << item_name << tail;
49         return ss.str();
50     }
51
52     return std::string(base);
53 }
54
55 /*
56  * @brief 噂の、町やモンスターを表すトークンを得る
57  * @param rumor rumor.txt (rumor_j.txt)の1行
58  * @return トークン読み込み成否 とトークン群の配列
59  * @todo tmp_tokensを使わず単なるsplitにすればもっと簡略化できそう
60  */
61 std::pair<bool, std::vector<std::string>> get_rumor_tokens(std::string rumor)
62 {
63     constexpr auto num_tokens = 3;
64     char *tmp_tokens[num_tokens];
65     if (tokenize(rumor.data() + 2, num_tokens, tmp_tokens, TOKENIZE_CHECKQUOTE) != num_tokens) {
66         msg_print(_("この情報は間違っている。", "This information is wrong."));
67         return { false, {} };
68     }
69
70     std::vector<std::string> tokens(std::begin(tmp_tokens), std::end(tmp_tokens));
71     return { true, tokens };
72 }
73
74 /*!
75  * @brief 固定アーティファクト番号とその定義を、ランダムに抽選する
76  * @param artifact_name rumor.txt (rumor_j.txt)の定義により、常に"*" (ランダム)
77  * @details 固定アーティファクト番号は欠番があるので、もし欠番だったら再抽選する
78  */
79 static std::pair<FixedArtifactId, const ArtifactType *> get_artifact_definition(std::string_view artifact_name)
80 {
81     const auto max_idx = enum2i(artifacts_info.rbegin()->first);
82     while (true) {
83         const auto a_idx = i2enum<FixedArtifactId>(get_rumor_num(artifact_name.data(), max_idx));
84         const auto &artifact = ArtifactsInfo::get_instance().get_artifact(a_idx);
85         if (!artifact.name.empty()) {
86             return { a_idx, &artifact };
87         }
88     }
89 }
90
91 void display_rumor(PlayerType *player_ptr, bool ex)
92 {
93     int section = (ex && (randint0(3) == 0)) ? 1 : 0;
94 #ifdef JP
95     auto opt_rumor = get_random_line_ja_only("rumors_j.txt", section, 10);
96 #else
97     auto opt_rumor = get_random_line("rumors.txt", section);
98 #endif
99     std::string rumor;
100     if (opt_rumor.has_value()) {
101         rumor = std::move(opt_rumor.value());
102     } else {
103         rumor = _("嘘の噂もある。", "Some rumors are wrong.");
104     }
105
106     if (!rumor.starts_with("R:")) {
107         msg_print(rumor);
108         return;
109     }
110
111     const auto &[is_correct, tokens] = get_rumor_tokens(rumor);
112     if (!is_correct) {
113         return;
114     }
115
116     concptr rumor_eff_format = nullptr;
117     std::string fullname;
118     const auto &category = tokens[0];
119     if (category == "ARTIFACT") {
120         const auto &artifact_name = tokens[1];
121         const auto &[a_idx, a_ptr] = get_artifact_definition(artifact_name);
122         const auto bi_id = lookup_baseitem_id(a_ptr->bi_key);
123         ItemEntity item;
124         item.prep(bi_id);
125         item.fixed_artifact_idx = a_idx;
126         item.ident = IDENT_STORE;
127         fullname = describe_flavor(player_ptr, &item, OD_NAME_ONLY);
128     } else if (category == "MONSTER") {
129         MonsterRaceInfo *r_ptr;
130         const auto &monster_name = tokens[1];
131
132         // @details プレイヤーもダミーで入っているので、1つ引いておかないと数が合わなくなる.
133         const auto monraces_size = static_cast<short>(monraces_info.size() - 1);
134         while (true) {
135             auto r_idx = i2enum<MonsterRaceId>(get_rumor_num(monster_name, monraces_size));
136             r_ptr = &monraces_info[r_idx];
137             if (!r_ptr->name.empty()) {
138                 break;
139             }
140         }
141
142         fullname = r_ptr->name;
143
144         if (!r_ptr->r_sights) {
145             r_ptr->r_sights++;
146         }
147     } else if (category == "DUNGEON") {
148         DUNGEON_IDX d_idx;
149         dungeon_type *d_ptr;
150         const auto dungeons_size = static_cast<short>(dungeons_info.size());
151         const auto &d_idx_str = tokens[1];
152         while (true) {
153             d_idx = get_rumor_num(d_idx_str, dungeons_size);
154             d_ptr = &dungeons_info[d_idx];
155             if (!d_ptr->name.empty()) {
156                 break;
157             }
158         }
159
160         fullname = d_ptr->name;
161         if (!max_dlv[d_idx]) {
162             max_dlv[d_idx] = d_ptr->mindepth;
163             rumor_eff_format = _("%sに帰還できるようになった。", "You can recall to %s.");
164         }
165     } else if (category == "TOWN") {
166         IDX t_idx;
167         const auto &town_name = tokens[1];
168         while (true) {
169             t_idx = get_rumor_num(town_name, VALID_TOWNS);
170             if (!towns_info[t_idx].name.empty()) {
171                 break;
172             }
173         }
174
175         fullname = towns_info[t_idx].name;
176         int32_t visit = (1UL << (t_idx - 1));
177         if ((t_idx != SECRET_TOWN) && !(player_ptr->visit & visit)) {
178             player_ptr->visit |= visit;
179             rumor_eff_format = _("%sに行ったことがある気がする。", "You feel you have been to %s.");
180         }
181     } else {
182         throw std::runtime_error("Unknown token exists in rumor.txt");
183     }
184
185     const auto rumor_msg = bind_rumor_name(tokens[2], fullname);
186     msg_print(rumor_msg);
187     if (rumor_eff_format) {
188         msg_print(nullptr);
189         msg_format(rumor_eff_format, fullname.data());
190     }
191 }