OSDN Git Service

Merge branch 'develop' into macos-develop
[hengbandforosx/hengbandosx.git] / src / info-reader / general-parser.cpp
1 #include "info-reader/general-parser.h"
2 #include "artifact/fixed-art-types.h"
3 #include "dungeon/quest.h"
4 #include "grid/feature.h"
5 #include "info-reader/feature-reader.h"
6 #include "info-reader/info-reader-util.h"
7 #include "info-reader/parse-error-types.h"
8 #include "info-reader/random-grid-effect-types.h"
9 #include "io/tokenizer.h"
10 #include "main/angband-headers.h"
11 #include "object-enchant/trg-types.h"
12 #include "realm/realm-types.h"
13 #include "system/artifact-type-definition.h"
14 #include "system/baseitem-info.h"
15 #include "system/building-type-definition.h"
16 #include "system/floor-type-definition.h"
17 #include "system/system-variables.h"
18 #include "util/angband-files.h"
19 #include "util/string-processor.h"
20 #include <string>
21
22 dungeon_grid letter[255];
23
24 /*!
25  * @brief パース関数に基づいてデータファイルからデータを読み取る /
26  * Initialize an "*_info" array, by parsing an ascii "template" file
27  * @param fp 読み取りに使うファイルポインタ
28  * @param buf 読み取りに使うバッファ領域
29  * @param head ヘッダ構造体
30  * @param parse_info_txt_line パース関数
31  * @return エラーコード, エラー行番号
32  */
33 std::tuple<errr, int> init_info_txt(FILE *fp, char *buf, angband_header *head, Parser parse_info_txt_line)
34 {
35     error_idx = -1;
36     auto error_line = 0;
37
38     util::SHA256 sha256;
39
40     while (true) {
41         // init_info_txtの呼び出し側のbufは1024バイト
42         const auto line_str = angband_fgets(fp, 1024);
43         if (!line_str) {
44             break;
45         }
46         error_line++;
47         const auto len = line_str->copy(buf, 1024 - 1);
48         buf[len] = '\0';
49         const std::string_view line = buf;
50         if (line.empty() || line.starts_with('#')) {
51             continue;
52         }
53
54         if (!line.substr(1).starts_with(':')) {
55             return { PARSE_ERROR_GENERIC, error_line };
56         }
57
58         if (line.starts_with('V')) {
59             continue;
60         }
61
62         // 文字コードの差異を吸収するため、日本語が含まれる可能性のある
63         // 「N:」「D:」「J:」はハッシュ計算から除外する
64         if (!line.starts_with('N') && !line.starts_with('D') && !line.starts_with('J')) {
65             sha256.update(line);
66         }
67
68         if (auto err = parse_info_txt_line(line, head); err != 0) {
69             return { err, error_line };
70         }
71     }
72
73     head->digest = sha256.digest();
74
75     return { PARSE_ERROR_NONE, error_line };
76 }
77
78 /*!
79  * @brief 地形情報の「F:」情報をパースする
80  * Process "F:<letter>:<terrain>:<cave_info>:<monster>:<object>:<ego>:<artifact>:<trap>:<special>" -- info for dungeon grid
81  * @param floor_ptr 現在フロアへの参照ポインタ
82  * @param buf 解析文字列
83  * @return エラーコード
84  */
85 parse_error_type parse_line_feature(FloorType *floor_ptr, char *buf)
86 {
87     if (init_flags & INIT_ONLY_BUILDINGS) {
88         return PARSE_ERROR_NONE;
89     }
90
91     char *zz[9];
92     int num = tokenize(buf + 2, 9, zz, 0);
93     if (num <= 1) {
94         return PARSE_ERROR_GENERIC;
95     }
96
97     int index = zz[0][0];
98     letter[index].feature = feat_none;
99     letter[index].monster = 0;
100     letter[index].object = 0;
101     letter[index].ego = EgoType::NONE;
102     letter[index].artifact = FixedArtifactId::NONE;
103     letter[index].trap = feat_none;
104     letter[index].cave_info = 0;
105     letter[index].special = 0;
106     letter[index].random = RANDOM_NONE;
107
108     switch (num) {
109     case 9:
110         letter[index].special = (int16_t)atoi(zz[8]);
111         [[fallthrough]];
112     case 8:
113         if ((zz[7][0] == '*') && !zz[7][1]) {
114             letter[index].random |= RANDOM_TRAP;
115         } else {
116             letter[index].trap = f_tag_to_index(zz[7]);
117             if (letter[index].trap < 0) {
118                 return PARSE_ERROR_UNDEFINED_TERRAIN_TAG;
119             }
120         }
121         [[fallthrough]];
122     case 7:
123         if (zz[6][0] == '*') {
124             letter[index].random |= RANDOM_ARTIFACT;
125             if (zz[6][1]) {
126                 letter[index].artifact = i2enum<FixedArtifactId>(atoi(zz[6] + 1));
127             }
128         } else if (zz[6][0] == '!') {
129             if (floor_ptr->is_in_quest()) {
130                 const auto &quest_list = QuestList::get_instance();
131                 letter[index].artifact = quest_list[floor_ptr->quest_number].reward_fa_id;
132             }
133         } else {
134             letter[index].artifact = i2enum<FixedArtifactId>(atoi(zz[6]));
135         }
136         [[fallthrough]];
137     case 6:
138         if (zz[5][0] == '*') {
139             letter[index].random |= RANDOM_EGO;
140             if (zz[5][1]) {
141                 letter[index].ego = i2enum<EgoType>(atoi(zz[5] + 1));
142             }
143         } else {
144             letter[index].ego = i2enum<EgoType>(atoi(zz[5]));
145         }
146         [[fallthrough]];
147     case 5:
148         if (zz[4][0] == '*') {
149             letter[index].random |= RANDOM_OBJECT;
150             if (zz[4][1]) {
151                 letter[index].object = (OBJECT_IDX)atoi(zz[4] + 1);
152             }
153         } else if (zz[4][0] == '!') {
154             if (floor_ptr->is_in_quest()) {
155                 const auto &quest = QuestList::get_instance()[floor_ptr->quest_number];
156                 if (quest.has_reward()) {
157                     const auto &artifact = quest.get_reward();
158                     if (artifact.gen_flags.has_not(ItemGenerationTraitType::INSTA_ART)) {
159                         letter[index].object = BaseitemList::get_instance().lookup_baseitem_id(artifact.bi_key);
160                     }
161                 }
162             }
163         } else {
164             letter[index].object = (OBJECT_IDX)atoi(zz[4]);
165         }
166         [[fallthrough]];
167     case 4:
168         if (zz[3][0] == '*') {
169             letter[index].random |= RANDOM_MONSTER;
170             if (zz[3][1]) {
171                 letter[index].monster = (MONSTER_IDX)atoi(zz[3] + 1);
172             }
173         } else if (zz[3][0] == 'c') {
174             if (!zz[3][1]) {
175                 return PARSE_ERROR_GENERIC;
176             }
177             letter[index].monster = -atoi(zz[3] + 1);
178         } else {
179             letter[index].monster = (MONSTER_IDX)atoi(zz[3]);
180         }
181         [[fallthrough]];
182     case 3:
183         letter[index].cave_info = atoi(zz[2]);
184         [[fallthrough]];
185     case 2:
186         if ((zz[1][0] == '*') && !zz[1][1]) {
187             letter[index].random |= RANDOM_FEATURE;
188         } else {
189             letter[index].feature = f_tag_to_index(zz[1]);
190             if (letter[index].feature < 0) {
191                 return PARSE_ERROR_UNDEFINED_TERRAIN_TAG;
192             }
193         }
194
195         break;
196     }
197
198     return PARSE_ERROR_NONE;
199 }
200
201 /*!
202  * @brief 地形情報の「B:」情報をパースする
203  * Process "B:<Index>:<Command>:..." -- Building definition
204  * @param buf 解析文字列
205  * @return エラーコード
206  */
207 parse_error_type parse_line_building(char *buf)
208 {
209     char *zz[1000];
210     char *s;
211
212 #ifdef JP
213     if (buf[2] == '$') {
214         return PARSE_ERROR_NONE;
215     }
216     s = buf + 2;
217 #else
218     if (buf[2] != '$') {
219         return PARSE_ERROR_NONE;
220     }
221     s = buf + 3;
222 #endif
223     int index = atoi(s);
224     s = angband_strchr(s, ':');
225     if (!s) {
226         return PARSE_ERROR_GENERIC;
227     }
228
229     *s++ = '\0';
230     if (!*s) {
231         return PARSE_ERROR_GENERIC;
232     }
233
234     switch (s[0]) {
235     case 'N': {
236         if (tokenize(s + 2, 3, zz, 0) == 3) {
237             strcpy(buildings[index].name, zz[0]);
238             strcpy(buildings[index].owner_name, zz[1]);
239             strcpy(buildings[index].owner_race, zz[2]);
240             break;
241         }
242
243         return PARSE_ERROR_TOO_FEW_ARGUMENTS;
244     }
245     case 'A': {
246         if (tokenize(s + 2, 8, zz, 0) >= 7) {
247             int action_index = atoi(zz[0]);
248             strcpy(buildings[index].act_names[action_index], zz[1]);
249             buildings[index].member_costs[action_index] = (PRICE)atoi(zz[2]);
250             buildings[index].other_costs[action_index] = (PRICE)atoi(zz[3]);
251             buildings[index].letters[action_index] = zz[4][0];
252             buildings[index].actions[action_index] = static_cast<int16_t>(atoi(zz[5]));
253             buildings[index].action_restr[action_index] = static_cast<int16_t>(atoi(zz[6]));
254             break;
255         }
256
257         return PARSE_ERROR_TOO_FEW_ARGUMENTS;
258     }
259     case 'C': {
260         auto pct_max = PLAYER_CLASS_TYPE_MAX;
261         auto n = tokenize(s + 2, pct_max, zz, 0);
262         for (auto i = 0; i < pct_max; i++) {
263             buildings[index].member_class[i] = (i < n) ? atoi(zz[i]) : 1;
264         }
265
266         break;
267     }
268     case 'R': {
269         auto n = tokenize(s + 2, MAX_RACES, zz, 0);
270         for (int i = 0; i < MAX_RACES; i++) {
271             buildings[index].member_race[i] = (i < n) ? atoi(zz[i]) : 1;
272         }
273
274         break;
275     }
276     case 'M': {
277         int n;
278         n = tokenize(s + 2, MAX_MAGIC, zz, 0);
279         for (int i = 0; i < MAX_MAGIC; i++) {
280             buildings[index].member_realm[i + 1] = ((i < n) ? static_cast<int16_t>(atoi(zz[i])) : 1);
281         }
282
283         break;
284     }
285     case 'Z': {
286         break;
287     }
288     default: {
289         return PARSE_ERROR_UNDEFINED_DIRECTIVE;
290     }
291     }
292
293     return PARSE_ERROR_NONE;
294 }