1 #include "io-dump/character-dump.h"
2 #include "artifact/fixed-art-types.h"
3 #include "avatar/avatar.h"
4 #include "cmd-building/cmd-building.h"
5 #include "dungeon/quest.h"
6 #include "flavor/flavor-describer.h"
7 #include "floor/floor-town.h"
8 #include "game-option/birth-options.h"
9 #include "game-option/game-play-options.h"
10 #include "inventory/inventory-slot-types.h"
11 #include "io-dump/player-status-dump.h"
12 #include "io-dump/special-class-dump.h"
13 #include "io/mutations-dump.h"
14 #include "io/write-diary.h"
15 #include "knowledge/knowledge-quests.h"
16 #include "main/angband-headers.h"
17 #include "market/arena-info-table.h"
18 #include "monster-race/monster-race.h"
19 #include "monster-race/race-flags1.h"
20 #include "monster/monster-describer.h"
21 #include "monster/monster-description-types.h"
22 #include "monster/monster-info.h"
23 #include "monster/monster-status.h"
24 #include "monster/smart-learn-types.h"
25 #include "object/object-info.h"
26 #include "pet/pet-util.h"
27 #include "player-info/alignment.h"
28 #include "player/player-status-flags.h"
29 #include "player/player-status-table.h"
30 #include "player/race-info-table.h"
31 #include "realm/realm-names-table.h"
32 #include "store/store-util.h"
33 #include "store/store.h"
34 #include "system/angband-version.h"
35 #include "system/building-type-definition.h"
36 #include "system/dungeon-info.h"
37 #include "system/floor-type-definition.h"
38 #include "system/item-entity.h"
39 #include "system/monster-entity.h"
40 #include "system/monster-race-info.h"
41 #include "system/player-type-definition.h"
42 #include "term/gameterm.h"
43 #include "term/z-form.h"
44 #include "util/enum-converter.h"
45 #include "util/int-char-converter.h"
46 #include "util/sort.h"
47 #include "util/string-processor.h"
48 #include "view/display-messages.h"
49 #include "world/world.h"
54 * @brief プレイヤーのペット情報をファイルにダンプする
55 * @param player_ptr プレイヤーへの参照ポインタ
58 static void dump_aux_pet(PlayerType *player_ptr, FILE *fff)
61 bool pet_settings = false;
62 for (int i = player_ptr->current_floor_ptr->m_max - 1; i >= 1; i--) {
63 auto *m_ptr = &player_ptr->current_floor_ptr->m_list[i];
65 if (!m_ptr->is_valid()) {
68 if (!m_ptr->is_pet()) {
72 if (!m_ptr->is_named() && (player_ptr->riding != i)) {
76 fprintf(fff, _("\n\n [主なペット]\n\n", "\n\n [Leading Pets]\n\n"));
80 const auto pet_name = monster_desc(player_ptr, m_ptr, MD_ASSUME_VISIBLE | MD_INDEF_VISIBLE);
81 fprintf(fff, "%s\n", pet_name.data());
88 fprintf(fff, _("\n\n [ペットへの命令]\n", "\n\n [Command for Pets]\n"));
90 fprintf(fff, _("\n ドアを開ける: %s", "\n Pets open doors: %s"),
91 (player_ptr->pet_extra_flags & PF_OPEN_DOORS) ? "ON" : "OFF");
93 fprintf(fff, _("\n アイテムを拾う: %s", "\n Pets pick up items: %s"),
94 (player_ptr->pet_extra_flags & PF_PICKUP_ITEMS) ? "ON" : "OFF");
96 fprintf(fff, _("\n テレポート系魔法を使う: %s", "\n Allow teleport: %s"),
97 (player_ptr->pet_extra_flags & PF_TELEPORT) ? "ON" : "OFF");
99 fprintf(fff, _("\n 攻撃魔法を使う: %s", "\n Allow cast attack spell: %s"),
100 (player_ptr->pet_extra_flags & PF_ATTACK_SPELL) ? "ON" : "OFF");
102 fprintf(fff, _("\n 召喚魔法を使う: %s", "\n Allow cast summon spell: %s"),
103 (player_ptr->pet_extra_flags & PF_SUMMON_SPELL) ? "ON" : "OFF");
105 fprintf(fff, _("\n プレイヤーを巻き込む範囲魔法を使う: %s", "\n Allow involve player in area spell: %s"),
106 (player_ptr->pet_extra_flags & PF_BALL_SPELL) ? "ON" : "OFF");
112 * @brief クエスト情報をファイルにダンプする
113 * @param player_ptr プレイヤーへの参照ポインタ
114 * @param fff ファイルポインタ
116 static void dump_aux_quest(PlayerType *player_ptr, FILE *fff)
118 fprintf(fff, _("\n\n [クエスト情報]\n", "\n\n [Quest Information]\n"));
120 const auto &quest_list = QuestList::get_instance();
121 std::vector<QuestId> quest_numbers;
122 for (const auto &[q_idx, quest] : quest_list) {
123 quest_numbers.push_back(q_idx);
126 ang_sort(player_ptr, quest_numbers.data(), &dummy, quest_numbers.size(), ang_sort_comp_quest_num, ang_sort_swap_quest_num);
129 do_cmd_knowledge_quests_completed(player_ptr, fff, quest_numbers);
131 do_cmd_knowledge_quests_failed(player_ptr, fff, quest_numbers);
136 * @brief 死の直前メッセージ並びに遺言をファイルにダンプする
137 * @param player_ptr プレイヤーへの参照ポインタ
138 * @param fff ファイルポインタ
140 static void dump_aux_last_message(PlayerType *player_ptr, FILE *fff)
142 if (!player_ptr->is_dead) {
146 if (!w_ptr->total_winner) {
147 fprintf(fff, _("\n [死ぬ直前のメッセージ]\n\n", "\n [Last Messages]\n\n"));
148 for (int i = std::min(message_num(), 30); i >= 0; i--) {
149 fprintf(fff, "> %s\n", message_str(i)->data());
156 if (player_ptr->last_message.empty()) {
160 fprintf(fff, _("\n [*勝利*メッセージ]\n\n", "\n [*Winning* Message]\n\n"));
161 fprintf(fff, " %s\n", player_ptr->last_message.data());
166 * @brief 帰還場所情報をファイルにダンプする
167 * @param fff ファイルポインタ
169 static void dump_aux_recall(FILE *fff)
171 fprintf(fff, _("\n [帰還場所]\n\n", "\n [Recall Depth]\n\n"));
172 for (const auto &d_ref : dungeons_info) {
175 if (d_ref.idx == 0 || !d_ref.maxdepth) {
178 if (!max_dlv[d_ref.idx]) {
181 if (MonsterRace(d_ref.final_guardian).is_valid()) {
182 if (!monraces_info[d_ref.final_guardian].max_num) {
185 } else if (max_dlv[d_ref.idx] == d_ref.maxdepth) {
189 fprintf(fff, _(" %c%-12s: %3d 階\n", " %c%-16s: level %3d\n"), seiha ? '!' : ' ', d_ref.name.data(), (int)max_dlv[d_ref.idx]);
194 * @brief オプション情報をファイルにダンプする
195 * @param fff ファイルポインタ
197 static void dump_aux_options(FILE *fff)
199 fprintf(fff, _("\n [オプション設定]\n", "\n [Option Settings]\n"));
201 fprintf(fff, _("\n 保存モード: ON", "\n Preserve Mode: ON"));
203 fprintf(fff, _("\n 保存モード: OFF", "\n Preserve Mode: OFF"));
206 if (ironman_small_levels) {
207 fprintf(fff, _("\n 小さいダンジョン: ALWAYS", "\n Small Levels: ALWAYS"));
208 } else if (always_small_levels) {
209 fprintf(fff, _("\n 小さいダンジョン: ON", "\n Small Levels: ON"));
210 } else if (small_levels) {
211 fprintf(fff, _("\n 小さいダンジョン: ENABLED", "\n Small Levels: ENABLED"));
213 fprintf(fff, _("\n 小さいダンジョン: OFF", "\n Small Levels: OFF"));
217 fprintf(fff, _("\n 元祖の町のみ: ON", "\n Vanilla Town: ON"));
218 } else if (lite_town) {
219 fprintf(fff, _("\n 小規模な町: ON", "\n Lite Town: ON"));
223 fprintf(fff, _("\n 店なし: ON", "\n No Shops: ON"));
226 if (ironman_downward) {
227 fprintf(fff, _("\n 階段を上がれない: ON", "\n Diving Only: ON"));
231 fprintf(fff, _("\n 普通でない部屋: ON", "\n Unusual Rooms: ON"));
234 if (ironman_nightmare) {
235 fprintf(fff, _("\n 悪夢モード: ON", "\n Nightmare Mode: ON"));
238 if (ironman_empty_levels) {
239 fprintf(fff, _("\n アリーナ: ALWAYS", "\n Arena Levels: ALWAYS"));
240 } else if (empty_levels) {
241 fprintf(fff, _("\n アリーナ: ENABLED", "\n Arena Levels: ENABLED"));
243 fprintf(fff, _("\n アリーナ: OFF", "\n Arena Levels: OFF"));
248 if (w_ptr->noscore) {
249 fprintf(fff, _("\n 何か不正なことをしてしまっています。\n", "\n You have done something illegal.\n"));
256 * @brief 闘技場の情報をファイルにダンプする
257 * @param player_ptr プレイヤーへの参照ポインタ
258 * @param fff ファイルポインタ
260 static void dump_aux_arena(PlayerType *player_ptr, FILE *fff)
262 if (lite_town || vanilla_town) {
266 const auto arena_number = player_ptr->arena_number;
267 if (arena_number < 0) {
268 if (arena_number <= ARENA_DEFEATED_OLD_VER) {
269 fprintf(fff, _("\n 闘技場: 敗北\n", "\n Arena: Defeated\n"));
271 constexpr auto mes = _("\n 闘技場: %d回戦で%sの前に敗北\n", "\n Arena: Defeated by %s in the %d%s fight\n");
272 const auto &arena = arena_info[-1 - arena_number];
273 const auto &arena_monrace = monraces_info[arena.r_idx];
275 fprintf(fff, mes, -arena_number, arena_monrace.name.data());
277 fprintf(fff, mes, arena_monrace.name.data(), -arena_number, get_ordinal_number_suffix(-arena_number).data());
285 if (arena_number > MAX_ARENA_MONS + 2) {
286 fprintf(fff, _("\n 闘技場: 真のチャンピオン\n", "\n Arena: True Champion\n"));
291 if (arena_number > MAX_ARENA_MONS - 1) {
292 fprintf(fff, _("\n 闘技場: チャンピオン\n", "\n Arena: Champion\n"));
297 const auto victory_count = arena_number > MAX_ARENA_MONS ? MAX_ARENA_MONS : arena_number;
299 fprintf(fff, "\n 闘技場: %2d勝\n", victory_count);
301 fprintf(fff, "\n Arena: %2d %s\n", victory_count, (arena_number > 1) ? "Victories" : "Victory");
307 * @brief 撃破モンスターの情報をファイルにダンプする
308 * @param fff ファイルポインタ
310 static void dump_aux_monsters(PlayerType *player_ptr, FILE *fff)
312 fprintf(fff, _("\n [倒したモンスター]\n\n", "\n [Defeated Monsters]\n\n"));
314 /* Allocate the "who" array */
316 std::vector<MonsterRaceId> who;
318 /* Count monster kills */
320 for (const auto &[monrace_id, monrace] : monraces_info) {
321 /* Ignore unused index */
322 if (!MonsterRace(monrace_id).is_valid()) {
326 if (monrace.kind_flags.has(MonsterKindType::UNIQUE)) {
327 auto dead = (monrace.max_num == 0);
331 /* Add a unique monster to the list */
332 who.push_back(monrace.idx);
338 if (monrace.r_pkills > 0) {
339 norm_total += monrace.r_pkills;
343 /* No monsters is defeated */
344 if (norm_total < 1) {
345 fprintf(fff, _("まだ敵を倒していません。\n", "You have defeated no enemies yet.\n"));
349 auto uniq_total = static_cast<int>(who.size());
350 /* Defeated more than one normal monsters */
351 if (uniq_total == 0) {
353 fprintf(fff, "%d体の敵を倒しています。\n", norm_total);
355 fprintf(fff, "You have defeated %d %s.\n", norm_total, norm_total == 1 ? "enemy" : "enemies");
360 /* Defeated more than one unique monsters */
362 fprintf(fff, "%d体のユニーク・モンスターを含む、合計%d体の敵を倒しています。\n", uniq_total, norm_total);
364 fprintf(fff, "You have defeated %d %s including %d unique monster%s in total.\n", norm_total, norm_total == 1 ? "enemy" : "enemies", uniq_total,
365 (uniq_total == 1 ? "" : "s"));
368 /* Sort the array by dungeon depth of monsters */
369 ang_sort(player_ptr, who.data(), &why, uniq_total, ang_sort_comp_hook, ang_sort_swap_hook);
370 fprintf(fff, _("\n《上位%d体のユニーク・モンスター》\n", "\n< Unique monsters top %d >\n"), std::min(uniq_total, 10));
373 for (auto it = who.rbegin(); it != who.rend() && std::distance(who.rbegin(), it) < 10; it++) {
374 auto *r_ptr = &monraces_info[*it];
375 if (r_ptr->defeat_level && r_ptr->defeat_time) {
376 strnfmt(buf, sizeof(buf), _(" - レベル%2d - %d:%02d:%02d", " - level %2d - %d:%02d:%02d"), r_ptr->defeat_level, r_ptr->defeat_time / (60 * 60),
377 (r_ptr->defeat_time / 60) % 60, r_ptr->defeat_time % 60);
382 auto name = str_separate(r_ptr->name, 40);
383 fprintf(fff, _(" %-40s (レベル%3d)%s\n", " %-40s (level %3d)%s\n"), name.front().data(), (int)r_ptr->level, buf);
384 for (auto i = 1U; i < name.size(); ++i) {
385 fprintf(fff, " %s\n", name[i].data());
391 * @brief 元種族情報をファイルにダンプする
392 * @param player_ptr プレイヤーへの参照ポインタ
393 * @param fff ファイルポインタ
395 static void dump_aux_race_history(PlayerType *player_ptr, FILE *fff)
397 if (!player_ptr->old_race1 && !player_ptr->old_race2) {
401 fprintf(fff, _("\n\n あなたは%sとして生まれた。", "\n\n You were born as %s."), race_info[enum2i(player_ptr->start_race)].title);
402 for (int i = 0; i < MAX_RACES; i++) {
403 if (enum2i(player_ptr->start_race) == i) {
407 if (!(player_ptr->old_race1 & 1UL << i)) {
411 if (!(player_ptr->old_race2 & 1UL << (i - 32))) {
416 fprintf(fff, _("\n あなたはかつて%sだった。", "\n You were a %s before."), race_info[i].title);
423 * @brief 元魔法領域情報をファイルにダンプする
424 * @param player_ptr プレイヤーへの参照ポインタ
425 * @param fff ファイルポインタ
427 static void dump_aux_realm_history(PlayerType *player_ptr, FILE *fff)
429 if (player_ptr->old_realm == 0) {
434 for (int i = 0; i < MAX_MAGIC; i++) {
435 if (!(player_ptr->old_realm & 1UL << i)) {
438 fprintf(fff, _("\n あなたはかつて%s魔法を使えた。", "\n You were able to use %s magic before."), realm_names[i + 1]);
445 * @brief 徳の情報をファイルにダンプする
446 * @param player_ptr プレイヤーへの参照ポインタ
447 * @param fff ファイルポインタ
449 static void dump_aux_virtues(PlayerType *player_ptr, FILE *fff)
451 fprintf(fff, _("\n\n [自分に関する情報]\n\n", "\n\n [HP-rate & Max stat & Virtues]\n\n"));
453 int percent = (int)(((long)player_ptr->player_hp[PY_MAX_LEVEL - 1] * 200L) / (2 * player_ptr->hitdie + ((PY_MAX_LEVEL - 1 + 3) * (player_ptr->hitdie + 1))));
456 if (player_ptr->knowledge & KNOW_HPRATE) {
457 fprintf(fff, "現在の体力ランク : %d/100\n\n", percent);
459 fprintf(fff, "現在の体力ランク : ???\n\n");
461 fprintf(fff, "能力の最大値\n");
463 if (player_ptr->knowledge & KNOW_HPRATE) {
464 fprintf(fff, "Your current Life Rating is %d/100.\n\n", percent);
466 fprintf(fff, "Your current Life Rating is ???.\n\n");
468 fprintf(fff, "Limits of maximum stats\n");
470 for (int v_nr = 0; v_nr < A_MAX; v_nr++) {
471 if ((player_ptr->knowledge & KNOW_STAT) || player_ptr->stat_max[v_nr] == player_ptr->stat_max_max[v_nr]) {
472 fprintf(fff, "%s 18/%d\n", stat_names[v_nr], player_ptr->stat_max_max[v_nr] - 18);
474 fprintf(fff, "%s ???\n", stat_names[v_nr]);
478 std::string alg = PlayerAlignment(player_ptr).get_alignment_description();
479 fprintf(fff, _("\n属性 : %s\n", "\nYour alignment : %s\n"), alg.data());
481 dump_virtues(player_ptr, fff);
485 * @brief 突然変異の情報をファイルにダンプする
486 * @param player_ptr プレイヤーへの参照ポインタ
487 * @param fff ファイルポインタ
489 static void dump_aux_mutations(PlayerType *player_ptr, FILE *fff)
491 if (player_ptr->muta.any()) {
492 fprintf(fff, _("\n\n [突然変異]\n\n", "\n\n [Mutations]\n\n"));
493 dump_mutations(player_ptr, fff);
498 * @brief 所持品の情報をファイルにダンプする
499 * @param player_ptr プレイヤーへの参照ポインタ
500 * @param fff ファイルポインタ
502 static void dump_aux_equipment_inventory(PlayerType *player_ptr, FILE *fff)
504 if (player_ptr->equip_cnt) {
505 fprintf(fff, _(" [キャラクタの装備]\n\n", " [Character Equipment]\n\n"));
506 for (int i = INVEN_MAIN_HAND; i < INVEN_TOTAL; i++) {
507 auto item_name = describe_flavor(player_ptr, &player_ptr->inventory_list[i], 0);
508 auto is_two_handed = ((i == INVEN_MAIN_HAND) && can_attack_with_sub_hand(player_ptr));
509 is_two_handed |= ((i == INVEN_SUB_HAND) && can_attack_with_main_hand(player_ptr));
510 if (is_two_handed && has_two_handed_weapons(player_ptr)) {
511 item_name = _("(武器を両手持ち)", "(wielding with two-hands)");
514 fprintf(fff, "%c) %s\n", index_to_label(i), item_name.data());
517 fprintf(fff, "\n\n");
520 fprintf(fff, _(" [キャラクタの持ち物]\n\n", " [Character Inventory]\n\n"));
522 for (int i = 0; i < INVEN_PACK; i++) {
523 if (!player_ptr->inventory_list[i].is_valid()) {
527 const auto item_name = describe_flavor(player_ptr, &player_ptr->inventory_list[i], 0);
528 fprintf(fff, "%c) %s\n", index_to_label(i), item_name.data());
531 fprintf(fff, "\n\n");
535 * @brief 我が家と博物館のオブジェクト情報をファイルにダンプする
536 * @param fff ファイルポインタ
538 static void dump_aux_home_museum(PlayerType *player_ptr, FILE *fff)
540 const auto *store_ptr = &towns_info[1].stores[StoreSaleType::HOME];
541 if (store_ptr->stock_num) {
542 fprintf(fff, _(" [我が家のアイテム]\n", " [Home Inventory]\n"));
544 for (auto i = 0; i < store_ptr->stock_num; i++) {
546 fprintf(fff, _("\n ( %d ページ )\n", "\n ( page %d )\n"), page++);
549 const auto item_name = describe_flavor(player_ptr, &store_ptr->stock[i], 0);
550 fprintf(fff, "%c) %s\n", I2A(i % 12), item_name.data());
553 fprintf(fff, "\n\n");
556 store_ptr = &towns_info[1].stores[StoreSaleType::MUSEUM];
558 if (store_ptr->stock_num == 0) {
562 fprintf(fff, _(" [博物館のアイテム]\n", " [Museum]\n"));
565 for (auto i = 0; i < store_ptr->stock_num; i++) {
567 fprintf(fff, _("\n ( %d ページ )\n", "\n ( page %d )\n"), page++);
570 const auto item_name = describe_flavor(player_ptr, &store_ptr->stock[i], 0);
571 fprintf(fff, "%c) %s\n", I2A(i % 12), item_name.data());
574 fprintf(fff, "\n\n");
578 * @brief チェックサム情報を出力 / Get check sum in string form
579 * @return チェックサム情報の文字列
581 static std::string get_check_sum(void)
583 static constexpr auto headers = {
586 &class_magics_header,
587 &class_skills_header,
596 for (const auto *header : headers) {
597 sha256.update(header->digest.data(), header->digest.size());
600 return util::to_string(sha256.digest());
604 * @brief ダンプ出力のメインルーチン
605 * Output the character dump to a file
606 * @param player_ptr プレイヤーへの参照ポインタ
607 * @param fff ファイルポインタ
610 void make_character_dump(PlayerType *player_ptr, FILE *fff)
612 TermCenteredOffsetSetter tos(MAIN_TERM_MIN_COLS, std::nullopt);
614 fprintf(fff, _(" [%s キャラクタ情報]\n\n", " [%s Character Dump]\n\n"), get_version().data());
616 dump_aux_player_status(player_ptr, fff);
617 dump_aux_last_message(player_ptr, fff);
618 dump_aux_options(fff);
619 dump_aux_recall(fff);
620 dump_aux_quest(player_ptr, fff);
621 dump_aux_arena(player_ptr, fff);
622 dump_aux_monsters(player_ptr, fff);
623 dump_aux_virtues(player_ptr, fff);
624 dump_aux_race_history(player_ptr, fff);
625 dump_aux_realm_history(player_ptr, fff);
626 dump_aux_class_special(player_ptr, fff);
627 dump_aux_mutations(player_ptr, fff);
628 dump_aux_pet(player_ptr, fff);
630 dump_aux_equipment_inventory(player_ptr, fff);
631 dump_aux_home_museum(player_ptr, fff);
633 // ダンプの幅をはみ出さないように48文字目以降を切り捨てる
634 const auto checksum = get_check_sum().erase(48);
635 fprintf(fff, _(" [チェックサム: \"%s\"]\n\n", " [Check Sum: \"%s\"]\n\n"), checksum.data());