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"
25 bool write_level; //!< @todo *抹殺* したい…
30 * @brief Return suffix of ordinal number
32 * @return pointer of suffix string.
34 std::string get_ordinal_number_suffix(int num)
36 num = std::abs(num) % 100;
39 return (num == 11) ? "th" : "st";
41 return (num == 12) ? "th" : "nd";
43 return (num == 13) ? "th" : "rd";
52 * @param fff ファイルへのポインタ
53 * @param disable_diary 日記への追加を無効化する場合TRUE
54 * @return ファイルがあったらTRUE、なかったらFALSE
55 * @todo files.c に移すことも検討する?
57 static bool open_diary_file(FILE **fff, bool *disable_diary)
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);
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());
71 *disable_diary = true;
76 * @brief フロア情報を日記に追加する
77 * @param player_ptr プレイヤーへの参照ポインタ
78 * @return クエストIDとレベルノートのペア
80 static std::pair<QuestId, std::string> write_floor(const FloorType &floor)
82 auto q_idx = floor.get_quest_id();
84 return std::make_pair(q_idx, std::string());
87 if (floor.inside_arena) {
88 return std::make_pair(q_idx, std::string(_("アリーナ:", "Arena:")));
91 if (!floor.dun_level) {
92 return std::make_pair(q_idx, std::string(_("地上:", "Surface:")));
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:")));
99 const auto &dungeon = floor.get_dungeon_definition();
101 const auto desc = format("%d階(%s):", floor.dun_level, dungeon.name.data());
103 const auto desc = format("%s L%d:", dungeon.name.data(), floor.dun_level);
105 return std::make_pair(q_idx, desc);
109 * @brief ペットに関する日記を追加する
111 * @param num 日記へ追加する内容番号
112 * @param note 日記内容のIDに応じた文字列参照ポインタ
114 static void write_diary_pet(FILE *fff, int num, std::string_view note)
117 case RECORD_NAMED_PET_NAME:
118 fprintf(fff, _("%sを旅の友にすることに決めた。\n", "decided to travel together with %s.\n"), note.data());
120 case RECORD_NAMED_PET_UNNAME:
121 fprintf(fff, _("%sの名前を消した。\n", "unnamed %s.\n"), note.data());
123 case RECORD_NAMED_PET_DISMISS:
124 fprintf(fff, _("%sを解放した。\n", "dismissed %s.\n"), note.data());
126 case RECORD_NAMED_PET_DEATH:
127 fprintf(fff, _("%sが死んでしまった。\n", "%s died.\n"), note.data());
129 case RECORD_NAMED_PET_MOVED:
130 fprintf(fff, _("%sをおいて別のマップへ移動した。\n", "moved to another map leaving %s behind.\n"), note.data());
132 case RECORD_NAMED_PET_LOST_SIGHT:
133 fprintf(fff, _("%sとはぐれてしまった。\n", "lost sight of %s.\n"), note.data());
135 case RECORD_NAMED_PET_DESTROY:
136 fprintf(fff, _("%sが*破壊*によって消え去った。\n", "%s was killed by *destruction*.\n"), note.data());
138 case RECORD_NAMED_PET_EARTHQUAKE:
139 fprintf(fff, _("%sが岩石に押し潰された。\n", "%s was crushed by falling rocks.\n"), note.data());
141 case RECORD_NAMED_PET_GENOCIDE:
142 fprintf(fff, _("%sが抹殺によって消え去った。\n", "%s was a victim of genocide.\n"), note.data());
144 case RECORD_NAMED_PET_WIZ_ZAP:
145 fprintf(fff, _("%sがデバッグコマンドによって消え去った。\n", "%s was removed by debug command.\n"), note.data());
147 case RECORD_NAMED_PET_TELE_LEVEL:
148 fprintf(fff, _("%sがテレポート・レベルによって消え去った。\n", "%s was lost after teleporting a level.\n"), note.data());
150 case RECORD_NAMED_PET_BLAST:
151 fprintf(fff, _("%sを爆破した。\n", "blasted %s.\n"), note.data());
153 case RECORD_NAMED_PET_HEAL_LEPER:
154 fprintf(fff, _("%sの病気が治り旅から外れた。\n", "%s was healed and left.\n"), note.data());
156 case RECORD_NAMED_PET_COMPACT:
157 fprintf(fff, _("%sがモンスター情報圧縮によって消え去った。\n", "%s was lost when the monster list was pruned.\n"), note.data());
159 case RECORD_NAMED_PET_LOSE_PARENT:
160 fprintf(fff, _("%sの召喚者が既にいないため消え去った。\n", "%s disappeared because its summoner left.\n"), note.data());
169 * @brief 日記にクエストに関するメッセージを追加する
171 * @param num 日記内容のIDに応じた番号
174 int exe_write_diary_quest(PlayerType *player_ptr, DiaryKind dk, QuestId num)
176 static bool disable_diary = false;
179 extract_day_hour_min(player_ptr, &day, &hour, &min);
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;
194 const auto &[q_idx, note_level] = write_floor(floor);
197 if (!open_diary_file(&fff, &disable_diary)) {
201 bool do_level = true;
204 case DiaryKind::FIX_QUEST_C: {
205 if (any_bits(quest.flags, QUEST_FLAG_SILENT)) {
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());
213 case DiaryKind::FIX_QUEST_F: {
214 if (any_bits(quest.flags, QUEST_FLAG_SILENT)) {
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());
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());
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());
232 case DiaryKind::TO_QUEST: {
233 if (any_bits(quest.flags, QUEST_FLAG_SILENT)) {
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());
254 * @brief 日記にメッセージを追加する
256 * @param num 日記内容のIDに応じた数値
257 * @param note 日記内容のIDに応じた文字列
259 void exe_write_diary(PlayerType *player_ptr, DiaryKind dk, int num, std::string_view note)
261 static bool disable_diary = false;
264 extract_day_hour_min(player_ptr, &day, &hour, &min);
271 if (!open_diary_file(&fff, &disable_diary)) {
275 const auto &floor = *player_ptr->current_floor_ptr;
276 const auto &[q_idx, note_level] = write_floor(floor);
277 auto do_level = true;
279 case DiaryKind::DIALY:
280 if (day < MAX_DAYS) {
281 fprintf(fff, _("%d日目\n", "Day %d\n"), day);
283 fputs(_("*****日目\n", "Day *****\n"), fff);
288 case DiaryKind::DESCRIPTION:
290 fprintf(fff, "%s\n", note.data());
293 fprintf(fff, " %2d:%02d %20s %s\n", hour, min, note_level.data(), note.data());
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());
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());
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());
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()));
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()));
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()));
334 case DiaryKind::RECALL:
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()));
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());
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());
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());
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());
360 case DiaryKind::ARENA: {
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()));
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());
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);
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());
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());
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);
399 case DiaryKind::GAMESTART: {
400 time_t ct = time((time_t *)0);
403 fprintf(fff, "%s %s", note.data(), ctime(&ct));
405 fprintf(fff, " %2d:%02d %20s %s %s", hour, min, note_level.data(), note.data(), ctime(&ct));
410 case DiaryKind::NAMED_PET:
411 fprintf(fff, " %2d:%02d %20s ", hour, min, note_level.data());
412 write_diary_pet(fff, num, note.data());
414 case DiaryKind::WIZARD_LOG:
415 fprintf(fff, "%s\n", note.data());