OSDN Git Service

[Refactor] #2807 Renamed monster-race-definition.h to monster-race-info.h
[hengbandforosx/hengbandosx.git] / src / io / write-diary.cpp
1 /*!
2  * @brief 日記へのメッセージ追加処理
3  * @date 2020/03/08
4  * @author Hourier
5  */
6
7 #include "io/write-diary.h"
8 #include "dungeon/quest.h"
9 #include "info-reader/fixed-map-parser.h"
10 #include "io/files-util.h"
11 #include "market/arena-info-table.h"
12 #include "monster-race/monster-race.h"
13 #include "player/player-status.h"
14 #include "system/dungeon-info.h"
15 #include "system/floor-type-definition.h"
16 #include "system/monster-race-info.h"
17 #include "system/player-type-definition.h"
18 #include "util/angband-files.h"
19 #include "util/bit-flags-calculator.h"
20 #include "view/display-messages.h"
21 #include "world/world.h"
22
23 bool write_level; //!< @todo *抹殺* したい…
24
25 #ifdef JP
26 #else
27 /*!
28  * @brief Return suffix of ordinal number
29  * @param num number
30  * @return pointer of suffix string.
31  */
32 concptr get_ordinal_number_suffix(int num)
33 {
34     num = std::abs(num) % 100;
35     switch (num % 10) {
36     case 1:
37         return (num == 11) ? "th" : "st";
38     case 2:
39         return (num == 12) ? "th" : "nd";
40     case 3:
41         return (num == 13) ? "th" : "rd";
42     default:
43         return "th";
44     }
45 }
46 #endif
47
48 /*!
49  * @brief 日記ファイルを開く
50  * @param fff ファイルへのポインタ
51  * @param disable_diary 日記への追加を無効化する場合TRUE
52  * @return ファイルがあったらTRUE、なかったらFALSE
53  * @todo files.c に移すことも検討する?
54  */
55 static bool open_diary_file(FILE **fff, bool *disable_diary)
56 {
57     GAME_TEXT file_name[MAX_NLEN];
58     sprintf(file_name, _("playrecord-%s.txt", "playrec-%s.txt"), savefile_base);
59     char buf[1024];
60     path_build(buf, sizeof(buf), ANGBAND_DIR_USER, file_name);
61     *fff = angband_fopen(buf, "a");
62     if (*fff) {
63         return true;
64     }
65
66     msg_format(_("%s を開くことができませんでした。プレイ記録を一時停止します。", "Failed to open %s. Play-Record is disabled temporarily."), buf);
67     msg_print(nullptr);
68     *disable_diary = true;
69     return false;
70 }
71
72 /*!
73  * @brief フロア情報を日記に追加する
74  * @param player_ptr プレイヤーへの参照ポインタ
75  * @return クエストID
76  */
77 static QuestId write_floor(PlayerType *player_ptr, concptr *note_level, char *note_level_buf)
78 {
79     auto *floor_ptr = player_ptr->current_floor_ptr;
80     auto q_idx = quest_number(player_ptr, floor_ptr->dun_level);
81     if (!write_level) {
82         return q_idx;
83     }
84
85     if (floor_ptr->inside_arena) {
86         *note_level = _("アリーナ:", "Arena:");
87     } else if (!floor_ptr->dun_level) {
88         *note_level = _("地上:", "Surface:");
89     } else if (inside_quest(q_idx) && quest_type::is_fixed(q_idx) && !((q_idx == QuestId::OBERON) || (q_idx == QuestId::SERPENT))) {
90         *note_level = _("クエスト:", "Quest:");
91     } else {
92 #ifdef JP
93         sprintf(note_level_buf, "%d階(%s):", (int)floor_ptr->dun_level, dungeons_info[player_ptr->dungeon_idx].name.data());
94 #else
95         sprintf(note_level_buf, "%s L%d:", dungeons_info[player_ptr->dungeon_idx].name.data(), (int)floor_ptr->dun_level);
96 #endif
97         *note_level = note_level_buf;
98     }
99
100     return q_idx;
101 }
102
103 /*!
104  * @brief ペットに関する日記を追加する
105  * @param fff 日記ファイル
106  * @param num 日記へ追加する内容番号
107  * @param note 日記内容のIDに応じた文字列参照ポインタ
108  */
109 static void write_diary_pet(FILE *fff, int num, concptr note)
110 {
111     switch (num) {
112     case RECORD_NAMED_PET_NAME:
113         fprintf(fff, _("%sを旅の友にすることに決めた。\n", "decided to travel together with %s.\n"), note);
114         break;
115     case RECORD_NAMED_PET_UNNAME:
116         fprintf(fff, _("%sの名前を消した。\n", "unnamed %s.\n"), note);
117         break;
118     case RECORD_NAMED_PET_DISMISS:
119         fprintf(fff, _("%sを解放した。\n", "dismissed %s.\n"), note);
120         break;
121     case RECORD_NAMED_PET_DEATH:
122         fprintf(fff, _("%sが死んでしまった。\n", "%s died.\n"), note);
123         break;
124     case RECORD_NAMED_PET_MOVED:
125         fprintf(fff, _("%sをおいて別のマップへ移動した。\n", "moved to another map leaving %s behind.\n"), note);
126         break;
127     case RECORD_NAMED_PET_LOST_SIGHT:
128         fprintf(fff, _("%sとはぐれてしまった。\n", "lost sight of %s.\n"), note);
129         break;
130     case RECORD_NAMED_PET_DESTROY:
131         fprintf(fff, _("%sが*破壊*によって消え去った。\n", "%s was killed by *destruction*.\n"), note);
132         break;
133     case RECORD_NAMED_PET_EARTHQUAKE:
134         fprintf(fff, _("%sが岩石に押し潰された。\n", "%s was crushed by falling rocks.\n"), note);
135         break;
136     case RECORD_NAMED_PET_GENOCIDE:
137         fprintf(fff, _("%sが抹殺によって消え去った。\n", "%s was a victim of genocide.\n"), note);
138         break;
139     case RECORD_NAMED_PET_WIZ_ZAP:
140         fprintf(fff, _("%sがデバッグコマンドによって消え去った。\n", "%s was removed by debug command.\n"), note);
141         break;
142     case RECORD_NAMED_PET_TELE_LEVEL:
143         fprintf(fff, _("%sがテレポート・レベルによって消え去った。\n", "%s was lost after teleporting a level.\n"), note);
144         break;
145     case RECORD_NAMED_PET_BLAST:
146         fprintf(fff, _("%sを爆破した。\n", "blasted %s.\n"), note);
147         break;
148     case RECORD_NAMED_PET_HEAL_LEPER:
149         fprintf(fff, _("%sの病気が治り旅から外れた。\n", "%s was healed and left.\n"), note);
150         break;
151     case RECORD_NAMED_PET_COMPACT:
152         fprintf(fff, _("%sがモンスター情報圧縮によって消え去った。\n", "%s was lost when the monster list was pruned.\n"), note);
153         break;
154     case RECORD_NAMED_PET_LOSE_PARENT:
155         fprintf(fff, _("%sの召喚者が既にいないため消え去った。\n", "%s disappeared because its summoner left.\n"), note);
156         break;
157     default:
158         fprintf(fff, "\n");
159         break;
160     }
161 }
162
163 /**
164  * @brief 日記にクエストに関するメッセージを追加する
165  *
166  * @param type 日記内容のID
167  * @param num 日記内容のIDに応じた番号
168  * @return エラーコード
169  */
170 int exe_write_diary_quest(PlayerType *player_ptr, int type, QuestId num)
171 {
172     static bool disable_diary = false;
173
174     int day, hour, min;
175     extract_day_hour_min(player_ptr, &day, &hour, &min);
176
177     if (disable_diary) {
178         return -1;
179     }
180
181     auto old_quest = player_ptr->current_floor_ptr->quest_number;
182     const auto &quest_list = QuestList::get_instance();
183     const auto &q_ref = quest_list[num];
184     player_ptr->current_floor_ptr->quest_number = (q_ref.type == QuestKindType::RANDOM) ? QuestId::NONE : num;
185     init_flags = INIT_NAME_ONLY;
186     parse_fixed_map(player_ptr, QUEST_DEFINITION_LIST, 0, 0, 0, 0);
187     player_ptr->current_floor_ptr->quest_number = old_quest;
188
189     concptr note_level = "";
190     char note_level_buf[40];
191     write_floor(player_ptr, &note_level, note_level_buf);
192
193     FILE *fff = nullptr;
194     if (!open_diary_file(&fff, &disable_diary)) {
195         return -1;
196     }
197
198     bool do_level = true;
199
200     switch (type) {
201     case DIARY_FIX_QUEST_C: {
202         if (any_bits(q_ref.flags, QUEST_FLAG_SILENT)) {
203             break;
204         }
205
206         fprintf(fff, _(" %2d:%02d %20s クエスト「%s」を達成した。\n", " %2d:%02d %20s completed quest '%s'.\n"), hour, min, note_level, q_ref.name);
207         break;
208     }
209     case DIARY_FIX_QUEST_F: {
210         if (any_bits(q_ref.flags, QUEST_FLAG_SILENT)) {
211             break;
212         }
213
214         fprintf(fff, _(" %2d:%02d %20s クエスト「%s」から命からがら逃げ帰った。\n", " %2d:%02d %20s ran away from quest '%s'.\n"), hour, min, note_level, q_ref.name);
215         break;
216     }
217     case DIARY_RAND_QUEST_C: {
218         GAME_TEXT name[MAX_NLEN];
219         strcpy(name, monraces_info[q_ref.r_idx].name.data());
220         fprintf(fff, _(" %2d:%02d %20s ランダムクエスト(%s)を達成した。\n", " %2d:%02d %20s completed random quest '%s'\n"), hour, min, note_level, name);
221         break;
222     }
223     case DIARY_RAND_QUEST_F: {
224         GAME_TEXT name[MAX_NLEN];
225         strcpy(name, monraces_info[q_ref.r_idx].name.data());
226         fprintf(fff, _(" %2d:%02d %20s ランダムクエスト(%s)から逃げ出した。\n", " %2d:%02d %20s ran away from quest '%s'.\n"), hour, min, note_level, name);
227         break;
228     }
229     case DIARY_TO_QUEST: {
230         if (any_bits(q_ref.flags, QUEST_FLAG_SILENT)) {
231             break;
232         }
233
234         fprintf(fff, _(" %2d:%02d %20s クエスト「%s」へと突入した。\n", " %2d:%02d %20s entered the quest '%s'.\n"),
235             hour, min, note_level, q_ref.name);
236         break;
237     }
238     default:
239         break;
240     }
241
242     angband_fclose(fff);
243     if (do_level) {
244         write_level = false;
245     }
246
247     return 0;
248 }
249
250 /*!
251  * @brief 日記にメッセージを追加する /
252  * Take note to the diary.
253  * @param type 日記内容のID
254  * @param num 日記内容のIDに応じた数値
255  * @param note 日記内容のIDに応じた文字列参照ポインタ
256  * @return エラーコード
257  */
258 errr exe_write_diary(PlayerType *player_ptr, int type, int num, concptr note)
259 {
260     static bool disable_diary = false;
261
262     int day, hour, min;
263     extract_day_hour_min(player_ptr, &day, &hour, &min);
264
265     if (disable_diary) {
266         return -1;
267     }
268
269     FILE *fff = nullptr;
270     if (!open_diary_file(&fff, &disable_diary)) {
271         return -1;
272     }
273
274     concptr note_level = "";
275     char note_level_buf[40];
276     auto q_idx = write_floor(player_ptr, &note_level, note_level_buf);
277
278     bool do_level = true;
279     switch (type) {
280     case DIARY_DIALY: {
281         if (day < MAX_DAYS) {
282             fprintf(fff, _("%d日目\n", "Day %d\n"), day);
283         } else {
284             fputs(_("*****日目\n", "Day *****\n"), fff);
285         }
286
287         do_level = false;
288         break;
289     }
290     case DIARY_DESCRIPTION: {
291         if (num) {
292             fprintf(fff, "%s\n", note);
293             do_level = false;
294         } else {
295             fprintf(fff, " %2d:%02d %20s %s\n", hour, min, note_level, note);
296         }
297
298         break;
299     }
300     case DIARY_ART: {
301         fprintf(fff, _(" %2d:%02d %20s %sを発見した。\n", " %2d:%02d %20s discovered %s.\n"), hour, min, note_level, note);
302         break;
303     }
304     case DIARY_ART_SCROLL: {
305         fprintf(fff, _(" %2d:%02d %20s 巻物によって%sを生成した。\n", " %2d:%02d %20s created %s by scroll.\n"), hour, min, note_level, note);
306         break;
307     }
308     case DIARY_UNIQUE: {
309         fprintf(fff, _(" %2d:%02d %20s %sを倒した。\n", " %2d:%02d %20s defeated %s.\n"), hour, min, note_level, note);
310         break;
311     }
312     case DIARY_MAXDEAPTH: {
313         fprintf(fff, _(" %2d:%02d %20s %sの最深階%d階に到達した。\n", " %2d:%02d %20s reached level %d of %s for the first time.\n"), hour, min, note_level,
314             _(dungeons_info[player_ptr->dungeon_idx].name.data(), num),
315             _(num, dungeons_info[player_ptr->dungeon_idx].name.data()));
316         break;
317     }
318     case DIARY_TRUMP: {
319         fprintf(fff, _(" %2d:%02d %20s %s%sの最深階を%d階にセットした。\n", " %2d:%02d %20s reset recall level of %s to %d %s.\n"), hour, min, note_level, note,
320             _(dungeons_info[num].name.data(), (int)max_dlv[num]),
321             _((int)max_dlv[num], dungeons_info[num].name.data()));
322         break;
323     }
324     case DIARY_STAIR: {
325         concptr to = inside_quest(q_idx) && (quest_type::is_fixed(q_idx) && !((q_idx == QuestId::OBERON) || (q_idx == QuestId::SERPENT)))
326                          ? _("地上", "the surface")
327                      : !(player_ptr->current_floor_ptr->dun_level + num)
328                          ? _("地上", "the surface")
329                          : format(_("%d階", "level %d"), player_ptr->current_floor_ptr->dun_level + num);
330         fprintf(fff, _(" %2d:%02d %20s %sへ%s。\n", " %2d:%02d %20s %s %s.\n"), hour, min, note_level, _(to, note), _(note, to));
331         break;
332     }
333     case DIARY_RECALL: {
334         if (!num) {
335             fprintf(fff, _(" %2d:%02d %20s 帰還を使って%sの%d階へ下りた。\n", " %2d:%02d %20s recalled to dungeon level %d of %s.\n"),
336                 hour, min, note_level, _(dungeons_info[player_ptr->dungeon_idx].name.data(), (int)max_dlv[player_ptr->dungeon_idx]),
337                 _((int)max_dlv[player_ptr->dungeon_idx], dungeons_info[player_ptr->dungeon_idx].name.data()));
338         } else {
339             fprintf(fff, _(" %2d:%02d %20s 帰還を使って地上へと戻った。\n", " %2d:%02d %20s recalled from dungeon to surface.\n"), hour, min, note_level);
340         }
341
342         break;
343     }
344     case DIARY_TELEPORT_LEVEL: {
345         fprintf(fff, _(" %2d:%02d %20s レベル・テレポートで脱出した。\n", " %2d:%02d %20s got out using teleport level.\n"),
346             hour, min, note_level);
347         break;
348     }
349     case DIARY_BUY: {
350         fprintf(fff, _(" %2d:%02d %20s %sを購入した。\n", " %2d:%02d %20s bought %s.\n"), hour, min, note_level, note);
351         break;
352     }
353     case DIARY_SELL: {
354         fprintf(fff, _(" %2d:%02d %20s %sを売却した。\n", " %2d:%02d %20s sold %s.\n"), hour, min, note_level, note);
355         break;
356     }
357     case DIARY_ARENA: {
358         if (num < 0) {
359             int n = -num;
360             fprintf(fff, _(" %2d:%02d %20s 闘技場の%d%s回戦で、%sの前に敗れ去った。\n", " %2d:%02d %20s beaten by %s in the %d%s fight.\n"),
361                 hour, min, note_level, _(n, note), _("", n), _(note, get_ordinal_number_suffix(n)));
362             break;
363         }
364
365         fprintf(fff, _(" %2d:%02d %20s 闘技場の%d%s回戦(%s)に勝利した。\n", " %2d:%02d %20s won the %d%s fight (%s).\n"),
366             hour, min, note_level, num, _("", get_ordinal_number_suffix(num)), note);
367
368         if (num == MAX_ARENA_MONS) {
369             fprintf(fff, _("                 闘技場のすべての敵に勝利し、チャンピオンとなった。\n",
370                              "                 won all fights to become a Champion.\n"));
371             do_level = false;
372         }
373
374         break;
375     }
376     case DIARY_FOUND: {
377         fprintf(fff, _(" %2d:%02d %20s %sを識別した。\n", " %2d:%02d %20s identified %s.\n"), hour, min, note_level, note);
378         break;
379     }
380     case DIARY_WIZ_TELE: {
381         const auto &floor_ref = *player_ptr->current_floor_ptr;
382         concptr to = !floor_ref.is_in_dungeon()
383                          ? _("地上", "the surface")
384                          : format(_("%d階(%s)", "level %d of %s"), floor_ref.dun_level, dungeons_info[player_ptr->dungeon_idx].name.data());
385         fprintf(fff, _(" %2d:%02d %20s %sへとウィザード・テレポートで移動した。\n", " %2d:%02d %20s wizard-teleported to %s.\n"), hour, min, note_level, to);
386         break;
387     }
388     case DIARY_PAT_TELE: {
389         const auto &floor_ref = *player_ptr->current_floor_ptr;
390         concptr to = !floor_ref.is_in_dungeon()
391                          ? _("地上", "the surface")
392                          : format(_("%d階(%s)", "level %d of %s"), floor_ref.dun_level, dungeons_info[player_ptr->dungeon_idx].name.data());
393         fprintf(fff, _(" %2d:%02d %20s %sへとパターンの力で移動した。\n", " %2d:%02d %20s used Pattern to teleport to %s.\n"), hour, min, note_level, to);
394         break;
395     }
396     case DIARY_LEVELUP: {
397         fprintf(fff, _(" %2d:%02d %20s レベルが%dに上がった。\n", " %2d:%02d %20s reached player level %d.\n"), hour, min, note_level, num);
398         break;
399     }
400     case DIARY_GAMESTART: {
401         time_t ct = time((time_t *)0);
402         do_level = false;
403         if (num) {
404             fprintf(fff, "%s %s", note, ctime(&ct));
405         } else {
406             fprintf(fff, " %2d:%02d %20s %s %s", hour, min, note_level, note, ctime(&ct));
407         }
408
409         break;
410     }
411     case DIARY_NAMED_PET: {
412         fprintf(fff, " %2d:%02d %20s ", hour, min, note_level);
413         write_diary_pet(fff, num, note);
414         break;
415     }
416     case DIARY_WIZARD_LOG:
417         fprintf(fff, "%s\n", note);
418         break;
419     default:
420         break;
421     }
422
423     angband_fclose(fff);
424     if (do_level) {
425         write_level = false;
426     }
427
428     return 0;
429 }