OSDN Git Service

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