OSDN Git Service

[Refactor] ESCORT を新定義に合わせた
[hengbandforosx/hengbandosx.git] / src / view / display-lore.cpp
1 /*!
2  * @brief モンスターの思い出を表示する処理
3  * @date 2020/06/09
4  * @author Hourier
5  */
6
7 #include "view/display-lore.h"
8 #include "game-option/cheat-options.h"
9 #include "game-option/text-display-options.h"
10 #include "locale/english.h"
11 #include "locale/japanese.h"
12 #include "lore/lore-calculator.h"
13 #include "lore/lore-util.h"
14 #include "lore/monster-lore.h"
15 #include "monster-attack/monster-attack-table.h"
16 #include "monster-race/monster-race.h"
17 #include "monster-race/race-ability-flags.h"
18 #include "monster-race/race-flags1.h"
19 #include "monster-race/race-flags2.h"
20 #include "monster-race/race-flags3.h"
21 #include "monster-race/race-flags7.h"
22 #include "monster-race/race-indice-types.h"
23 #include "system/monster-race-info.h"
24 #include "system/player-type-definition.h"
25 #include "term/screen-processor.h"
26 #include "term/term-color-types.h"
27 #include "term/z-form.h"
28 #include "util/bit-flags-calculator.h"
29 #include "util/string-processor.h"
30 #include "view/display-messages.h"
31 #include "world/world.h"
32
33 /*!
34  * 英語の複数系記述用マクロ / Pluralizer.  Args(count, singular, plural)
35  */
36 #define plural(c, s, p) (((c) == 1) ? (s) : (p))
37
38 /*!
39  * @brief モンスター情報のヘッダを記述する
40  * Hack -- Display the "name" and "attr/chars" of a monster race
41  * @param r_idx モンスターの種族ID
42  */
43 void roff_top(MonsterRaceId r_idx)
44 {
45     auto *r_ptr = &monraces_info[r_idx];
46     char c1 = r_ptr->d_char;
47     char c2 = r_ptr->x_char;
48
49     TERM_COLOR a1 = r_ptr->d_attr;
50     TERM_COLOR a2 = r_ptr->x_attr;
51
52     term_erase(0, 0);
53     term_gotoxy(0, 0);
54
55 #ifdef JP
56 #else
57     if (r_ptr->kind_flags.has_not(MonsterKindType::UNIQUE)) {
58         term_addstr(-1, TERM_WHITE, "The ");
59     }
60 #endif
61
62     if (w_ptr->wizard || cheat_know) {
63         term_addstr(-1, TERM_WHITE, "[");
64         term_addstr(-1, TERM_L_BLUE, format("%d", enum2i(r_idx)));
65         term_addstr(-1, TERM_WHITE, "] ");
66     }
67
68     term_addstr(-1, TERM_WHITE, r_ptr->name);
69
70     term_addstr(-1, TERM_WHITE, " ('");
71     term_add_bigch(a1, c1);
72     term_addstr(-1, TERM_WHITE, "')");
73
74     term_addstr(-1, TERM_WHITE, "/('");
75     term_add_bigch(a2, c2);
76     term_addstr(-1, TERM_WHITE, "'):");
77 }
78
79 /*!
80  * @brief  モンスター情報の表示と共に画面を一時消去するサブルーチン /
81  * Hack -- describe the given monster race at the top of the screen
82  * @param r_idx モンスターの種族ID
83  * @param mode 表示オプション
84  */
85 void screen_roff(PlayerType *player_ptr, MonsterRaceId r_idx, monster_lore_mode mode)
86 {
87     msg_erase();
88     term_erase(0, 1);
89     hook_c_roff = c_roff;
90     process_monster_lore(player_ptr, r_idx, mode);
91     roff_top(r_idx);
92 }
93
94 /*!
95  * @brief モンスター情報の現在のウィンドウに表示する /
96  * Hack -- describe the given monster race in the current "term" window
97  * @param r_idx モンスターの種族ID
98  */
99 void display_roff(PlayerType *player_ptr)
100 {
101     for (int y = 0; y < game_term->hgt; y++) {
102         term_erase(0, y);
103     }
104
105     term_gotoxy(0, 1);
106     hook_c_roff = c_roff;
107     MonsterRaceId r_idx = player_ptr->monster_race_idx;
108     process_monster_lore(player_ptr, r_idx, MONSTER_LORE_NORMAL);
109     roff_top(r_idx);
110 }
111
112 /*!
113  * @brief モンスター詳細情報を自動スポイラー向けに出力する /
114  * Hack -- output description of the given monster race
115  * @param r_idx モンスターの種族ID
116  * @param roff_func 出力処理を行う関数ポインタ
117  * @todo ここのroff_funcの引数にFILE* を追加しないとspoiler_file をローカル関数化することができないと判明した、保留.
118  */
119 void output_monster_spoiler(MonsterRaceId r_idx, hook_c_roff_pf roff_func)
120 {
121     hook_c_roff = roff_func;
122     PlayerType dummy;
123
124     dummy.lev = 1;
125     dummy.max_plv = 1;
126     process_monster_lore(&dummy, r_idx, MONSTER_LORE_DEBUG);
127 }
128
129 static bool display_kill_unique(lore_type *lore_ptr)
130 {
131     if (lore_ptr->kind_flags.has_not(MonsterKindType::UNIQUE)) {
132         return false;
133     }
134
135     bool dead = (lore_ptr->r_ptr->max_num == 0);
136     if (lore_ptr->r_ptr->r_deaths) {
137         hooked_roff(format(_("%s^はあなたの先祖を %d 人葬っている", "%s^ has slain %d of your ancestors"), Who::who(lore_ptr->msex), lore_ptr->r_ptr->r_deaths));
138
139         if (dead) {
140             hooked_roff(
141                 _(format("が、すでに仇討ちは果たしている!"), format(", but you have avenged %s!  ", plural(lore_ptr->r_ptr->r_deaths, "him", "them"))));
142         } else {
143             hooked_roff(
144                 _(format("のに、まだ仇討ちを果たしていない。"), format(", who %s unavenged.  ", plural(lore_ptr->r_ptr->r_deaths, "remains", "remain"))));
145         }
146
147         hooked_roff("\n");
148     } else {
149         if (dead) {
150             hooked_roff(_("あなたはこの仇敵をすでに葬り去っている。", "You have slain this foe.  "));
151         } else {
152             hooked_roff(_("この仇敵はまだ生きている!", "This foe is still alive!  "));
153         }
154
155         hooked_roff("\n");
156     }
157
158     return true;
159 }
160
161 static void display_killed(lore_type *lore_ptr)
162 {
163     hooked_roff(_(format("このモンスターはあなたの先祖を %d 人葬っている", lore_ptr->r_ptr->r_deaths),
164         format("%d of your ancestors %s been killed by this creature, ", lore_ptr->r_ptr->r_deaths, plural(lore_ptr->r_ptr->r_deaths, "has", "have"))));
165
166     if (lore_ptr->r_ptr->r_pkills) {
167         hooked_roff(format(_("が、あなたはこのモンスターを少なくとも %d 体は倒している。", "and you have exterminated at least %d of the creatures.  "),
168             lore_ptr->r_ptr->r_pkills));
169     } else if (lore_ptr->r_ptr->r_tkills) {
170         hooked_roff(format(
171             _("が、あなたの先祖はこのモンスターを少なくとも %d 体は倒している。", "and your ancestors have exterminated at least %d of the creatures.  "),
172             lore_ptr->r_ptr->r_tkills));
173     } else {
174         hooked_roff(format(_("が、まだ%sを倒したことはない。", "and %s is not ever known to have been defeated.  "), Who::who(lore_ptr->msex)));
175     }
176 }
177
178 static void display_no_killed(lore_type *lore_ptr)
179 {
180     if (lore_ptr->r_ptr->r_pkills) {
181         hooked_roff(format(
182             _("あなたはこのモンスターを少なくとも %d 体は殺している。", "You have killed at least %d of these creatures.  "), lore_ptr->r_ptr->r_pkills));
183     } else if (lore_ptr->r_ptr->r_tkills) {
184         hooked_roff(format(_("あなたの先祖はこのモンスターを少なくとも %d 体は殺している。", "Your ancestors have killed at least %d of these creatures.  "),
185             lore_ptr->r_ptr->r_tkills));
186     } else {
187         hooked_roff(_("このモンスターを倒したことはない。", "No battles to the death are recalled.  "));
188     }
189 }
190
191 /*!
192  * @brief 生存数制限のあるモンスターの最大生存数を表示する
193  * @param lore_ptr モンスターの思い出構造体への参照ポインタ
194  * @details
195  * 一度も倒したことのないモンスターの情報は不明。
196  */
197 static void display_number_of_nazguls(lore_type *lore_ptr)
198 {
199     if (lore_ptr->mode != MONSTER_LORE_DEBUG && lore_ptr->r_ptr->r_tkills == 0) {
200         return;
201     }
202     if (!lore_ptr->r_ptr->population_flags.has(MonsterPopulationType::NAZGUL)) {
203         return;
204     }
205
206     int remain = lore_ptr->r_ptr->max_num;
207     int killed = lore_ptr->r_ptr->r_akills;
208     if (remain == 0) {
209 #ifdef JP
210         hooked_roff(format("%sはかつて %d 体存在した。", Who::who(lore_ptr->msex, (killed > 1)), killed));
211 #else
212         hooked_roff(format("You already killed all %d of %s.  ", killed, Who::whom(lore_ptr->msex, (killed > 1))));
213 #endif
214     } else {
215 #ifdef JP
216         hooked_roff(format("%sはまだ %d 体生きている。", Who::who(lore_ptr->msex, (remain + killed > 1)), remain));
217 #else
218         concptr be = (remain > 1) ? "are" : "is";
219         hooked_roff(format("%d of %s %s still alive.  ", remain, Who::whom(lore_ptr->msex, (remain + killed > 1)), be));
220 #endif
221     }
222 }
223
224 void display_kill_numbers(lore_type *lore_ptr)
225 {
226     if ((lore_ptr->mode & 0x02) != 0) {
227         return;
228     }
229
230     if (display_kill_unique(lore_ptr)) {
231         return;
232     }
233
234     if (lore_ptr->r_ptr->r_deaths == 0) {
235         display_no_killed(lore_ptr);
236     } else {
237         display_killed(lore_ptr);
238     }
239
240     display_number_of_nazguls(lore_ptr);
241
242     hooked_roff("\n");
243 }
244
245 /*!
246  * @brief どこに出没するかを表示する
247  * @param lore_ptr モンスターの思い出構造体への参照ポインタ
248  * @return たぬきならFALSE、それ以外はTRUE
249  */
250 bool display_where_to_appear(lore_type *lore_ptr)
251 {
252     lore_ptr->old = false;
253     if (lore_ptr->r_ptr->level == 0) {
254         hooked_roff(format(_("%s^は町に住み", "%s^ lives in the town"), Who::who(lore_ptr->msex)));
255         lore_ptr->old = true;
256     } else if (lore_ptr->r_ptr->r_tkills || lore_ptr->know_everything) {
257         if (depth_in_feet) {
258             hooked_roff(format(
259                 _("%s^は通常地下 %d フィートで出現し", "%s^ is normally found at depths of %d feet"), Who::who(lore_ptr->msex), lore_ptr->r_ptr->level * 50));
260         } else {
261             hooked_roff(format(_("%s^は通常地下 %d 階で出現し", "%s^ is normally found on dungeon level %d"), Who::who(lore_ptr->msex), lore_ptr->r_ptr->level));
262         }
263
264         lore_ptr->old = true;
265     }
266
267     if (lore_ptr->r_idx == MonsterRaceId::CHAMELEON) {
268         hooked_roff(_("、他のモンスターに化ける。", "and can take the shape of other monster."));
269         return false;
270     }
271
272     if (lore_ptr->old) {
273         hooked_roff(_("、", ", and "));
274     } else {
275         hooked_roff(format(_("%s^は", "%s^ "), Who::who(lore_ptr->msex)));
276         lore_ptr->old = true;
277     }
278
279     return true;
280 }
281
282 // @todo モンスターの速度表記はmonster_typeのオブジェクトメソッドにした方がベター
283 void display_monster_move(lore_type *lore_ptr)
284 {
285 #ifdef JP
286 #else
287     hooked_roff("moves");
288 #endif
289
290     display_random_move(lore_ptr);
291     if (lore_ptr->speed > STANDARD_SPEED) {
292         if (lore_ptr->speed > 139) {
293             hook_c_roff(TERM_RED, _("信じ難いほど", " incredibly"));
294         } else if (lore_ptr->speed > 134) {
295             hook_c_roff(TERM_ORANGE, _("猛烈に", " extremely"));
296         } else if (lore_ptr->speed > 129) {
297             hook_c_roff(TERM_ORANGE, _("非常に", " very"));
298         } else if (lore_ptr->speed > 124) {
299             hook_c_roff(TERM_UMBER, _("かなり", " fairly"));
300         } else if (lore_ptr->speed < 120) {
301             hook_c_roff(TERM_L_UMBER, _("やや", " somewhat"));
302         }
303         hook_c_roff(TERM_L_RED, _("素早く", " quickly"));
304     } else if (lore_ptr->speed < STANDARD_SPEED) {
305         if (lore_ptr->speed < 90) {
306             hook_c_roff(TERM_L_GREEN, _("信じ難いほど", " incredibly"));
307         } else if (lore_ptr->speed < 95) {
308             hook_c_roff(TERM_BLUE, _("非常に", " very"));
309         } else if (lore_ptr->speed < 100) {
310             hook_c_roff(TERM_BLUE, _("かなり", " fairly"));
311         } else if (lore_ptr->speed > 104) {
312             hook_c_roff(TERM_GREEN, _("やや", " somewhat"));
313         }
314         hook_c_roff(TERM_L_BLUE, _("ゆっくりと", " slowly"));
315     } else {
316         hooked_roff(_("普通の速さで", " at normal speed"));
317     }
318
319 #ifdef JP
320     hooked_roff("動いている");
321 #endif
322 }
323
324 void display_random_move(lore_type *lore_ptr)
325 {
326     if (lore_ptr->behavior_flags.has_none_of({ MonsterBehaviorType::RAND_MOVE_50, MonsterBehaviorType::RAND_MOVE_25 })) {
327         return;
328     }
329
330     if (lore_ptr->behavior_flags.has(MonsterBehaviorType::RAND_MOVE_50) && lore_ptr->behavior_flags.has(MonsterBehaviorType::RAND_MOVE_25)) {
331         hooked_roff(_("かなり", " extremely"));
332     } else if (lore_ptr->behavior_flags.has(MonsterBehaviorType::RAND_MOVE_50)) {
333         hooked_roff(_("幾分", " somewhat"));
334     } else if (lore_ptr->behavior_flags.has(MonsterBehaviorType::RAND_MOVE_25)) {
335         hooked_roff(_("少々", " a bit"));
336     }
337
338     hooked_roff(_("不規則に", " erratically"));
339     if (lore_ptr->speed != STANDARD_SPEED) {
340         hooked_roff(_("、かつ", ", and"));
341     }
342 }
343
344 void display_monster_never_move(lore_type *lore_ptr)
345 {
346     if (lore_ptr->behavior_flags.has_not(MonsterBehaviorType::NEVER_MOVE)) {
347         return;
348     }
349
350     if (lore_ptr->old) {
351         hooked_roff(_("、しかし", ", but "));
352     } else {
353         hooked_roff(format(_("%s^は", "%s^ "), Who::who(lore_ptr->msex)));
354         lore_ptr->old = true;
355     }
356
357     hooked_roff(_("侵入者を追跡しない", "does not deign to chase intruders"));
358 }
359
360 void display_monster_kind(lore_type *lore_ptr)
361 {
362     if (lore_ptr->kind_flags.has_none_of({ MonsterKindType::DRAGON, MonsterKindType::DEMON, MonsterKindType::GIANT, MonsterKindType::TROLL, MonsterKindType::ORC, MonsterKindType::ANGEL, MonsterKindType::QUANTUM, MonsterKindType::HUMAN })) {
363         hooked_roff(_("モンスター", " creature"));
364         return;
365     }
366
367     if (lore_ptr->kind_flags.has(MonsterKindType::DRAGON)) {
368         hook_c_roff(TERM_ORANGE, _("ドラゴン", " dragon"));
369     }
370
371     if (lore_ptr->kind_flags.has(MonsterKindType::DEMON)) {
372         hook_c_roff(TERM_VIOLET, _("デーモン", " demon"));
373     }
374
375     if (lore_ptr->kind_flags.has(MonsterKindType::GIANT)) {
376         hook_c_roff(TERM_L_UMBER, _("巨人", " giant"));
377     }
378
379     if (lore_ptr->kind_flags.has(MonsterKindType::TROLL)) {
380         hook_c_roff(TERM_BLUE, _("トロル", " troll"));
381     }
382
383     if (lore_ptr->kind_flags.has(MonsterKindType::ORC)) {
384         hook_c_roff(TERM_UMBER, _("オーク", " orc"));
385     }
386
387     if (lore_ptr->kind_flags.has(MonsterKindType::HUMAN)) {
388         hook_c_roff(TERM_L_WHITE, _("人間", " human"));
389     }
390
391     if (lore_ptr->kind_flags.has(MonsterKindType::QUANTUM)) {
392         hook_c_roff(TERM_VIOLET, _("量子生物", " quantum creature"));
393     }
394
395     if (lore_ptr->kind_flags.has(MonsterKindType::ANGEL)) {
396         hook_c_roff(TERM_YELLOW, _("天使", " angel"));
397     }
398 }
399
400 void display_monster_alignment(lore_type *lore_ptr)
401 {
402     if (lore_ptr->flags2 & RF2_ELDRITCH_HORROR) {
403         hook_c_roff(TERM_VIOLET, _("狂気を誘う", " sanity-blasting"));
404     }
405
406     if (lore_ptr->kind_flags.has(MonsterKindType::ANIMAL)) {
407         hook_c_roff(TERM_L_GREEN, _("自然界の", " natural"));
408     }
409
410     if (lore_ptr->kind_flags.has(MonsterKindType::EVIL)) {
411         hook_c_roff(TERM_L_DARK, _("邪悪なる", " evil"));
412     }
413
414     if (lore_ptr->kind_flags.has(MonsterKindType::GOOD)) {
415         hook_c_roff(TERM_YELLOW, _("善良な", " good"));
416     }
417
418     if (lore_ptr->kind_flags.has(MonsterKindType::UNDEAD)) {
419         hook_c_roff(TERM_VIOLET, _("アンデッドの", " undead"));
420     }
421
422     if (lore_ptr->kind_flags.has(MonsterKindType::AMBERITE)) {
423         hook_c_roff(TERM_VIOLET, _("アンバーの王族の", " Amberite"));
424     }
425 }
426
427 /*!
428  * @brief モンスターの経験値の思い出を表示する
429  * @param player_ptr プレイヤーの情報へのポインター
430  * @param lore_ptr モンスターの思い出の情報へのポインター
431  */
432 void display_monster_exp(PlayerType *player_ptr, lore_type *lore_ptr)
433 {
434 #ifdef JP
435     hooked_roff("を倒すことは");
436 #endif
437
438     // 最も経験値の多い金無垢の指輪(level 110、mexp 5000000)でも符号付き32bit整数に収まる
439     const auto base_exp = lore_ptr->r_ptr->mexp * lore_ptr->r_ptr->level * 3 / 2;
440     const auto player_factor = player_ptr->max_plv + 2;
441
442     const auto exp_integer = base_exp / player_factor;
443     const auto exp_decimal = ((base_exp % player_factor * 1000 / player_factor) + 5) / 10;
444
445 #ifdef JP
446     hooked_roff(format(" %d レベルのキャラクタにとって 約%d.%02d ポイントの経験となる。", player_ptr->lev, exp_integer, exp_decimal));
447 #else
448     hooked_roff(format(" is worth about %d.%02d point%s", exp_integer, exp_decimal, ((exp_integer == 1) && (exp_decimal == 0)) ? "" : "s"));
449
450     concptr ordinal;
451     switch (player_ptr->lev % 10) {
452     case 1:
453         ordinal = "st";
454         break;
455     case 2:
456         ordinal = "nd";
457         break;
458     case 3:
459         ordinal = "rd";
460         break;
461     default:
462         ordinal = "th";
463         break;
464     }
465
466     concptr vowel;
467     switch (player_ptr->lev) {
468     case 8:
469     case 11:
470     case 18:
471         vowel = "n";
472         break;
473     default:
474         vowel = "";
475         break;
476     }
477
478     hooked_roff(format(" for a%s %d%s level character.  ", vowel, player_ptr->lev, ordinal));
479 #endif
480 }
481
482 void display_monster_aura(lore_type *lore_ptr)
483 {
484     auto has_fire_aura = lore_ptr->aura_flags.has(MonsterAuraType::FIRE);
485     auto has_elec_aura = lore_ptr->aura_flags.has(MonsterAuraType::ELEC);
486     auto has_cold_aura = lore_ptr->aura_flags.has(MonsterAuraType::COLD);
487     if (has_fire_aura && has_elec_aura && has_cold_aura) {
488         hook_c_roff(
489             TERM_VIOLET, format(_("%s^は炎と氷とスパークに包まれている。", "%s^ is surrounded by flames, ice and electricity.  "), Who::who(lore_ptr->msex)));
490     } else if (has_fire_aura && has_elec_aura) {
491         hook_c_roff(TERM_L_RED, format(_("%s^は炎とスパークに包まれている。", "%s^ is surrounded by flames and electricity.  "), Who::who(lore_ptr->msex)));
492     } else if (has_fire_aura && has_cold_aura) {
493         hook_c_roff(TERM_BLUE, format(_("%s^は炎と氷に包まれている。", "%s^ is surrounded by flames and ice.  "), Who::who(lore_ptr->msex)));
494     } else if (has_cold_aura && has_elec_aura) {
495         hook_c_roff(TERM_L_GREEN, format(_("%s^は氷とスパークに包まれている。", "%s^ is surrounded by ice and electricity.  "), Who::who(lore_ptr->msex)));
496     } else if (has_fire_aura) {
497         hook_c_roff(TERM_RED, format(_("%s^は炎に包まれている。", "%s^ is surrounded by flames.  "), Who::who(lore_ptr->msex)));
498     } else if (has_cold_aura) {
499         hook_c_roff(TERM_BLUE, format(_("%s^は氷に包まれている。", "%s^ is surrounded by ice.  "), Who::who(lore_ptr->msex)));
500     } else if (has_elec_aura) {
501         hook_c_roff(TERM_L_BLUE, format(_("%s^はスパークに包まれている。", "%s^ is surrounded by electricity.  "), Who::who(lore_ptr->msex)));
502     }
503 }
504
505 void display_lore_this(PlayerType *player_ptr, lore_type *lore_ptr)
506 {
507     if ((lore_ptr->r_ptr->r_tkills == 0) && !lore_ptr->know_everything) {
508         return;
509     }
510
511 #ifdef JP
512     hooked_roff("この");
513 #else
514     if (lore_ptr->kind_flags.has(MonsterKindType::UNIQUE)) {
515         hooked_roff("Killing this");
516     } else {
517         hooked_roff("A kill of this");
518     }
519 #endif
520
521     display_monster_alignment(lore_ptr);
522     display_monster_kind(lore_ptr);
523     display_monster_exp(player_ptr, lore_ptr);
524 }
525
526 static void display_monster_escort_contents(lore_type *lore_ptr)
527 {
528     if (!lore_ptr->reinforce) {
529         return;
530     }
531
532     hooked_roff(_("護衛の構成は", "These escorts"));
533     if (lore_ptr->misc_flags.has(MonsterMiscType::ESCORT) || (lore_ptr->flags1 & RF1_ESCORTS)) {
534         hooked_roff(_("少なくとも", " at the least"));
535     }
536
537 #ifdef JP
538 #else
539     hooked_roff(" contain");
540     auto max_idx = lore_ptr->r_ptr->reinforces.size() - 1;
541     auto idx = 0 * max_idx;
542 #endif
543
544     for (auto [r_idx, dd, ds] : lore_ptr->r_ptr->reinforces) {
545         auto is_reinforced = MonsterRace(r_idx).is_valid();
546 #ifndef JP
547         const char *prefix = (idx == 0) ? " " : (idx == max_idx) ? " and "
548                                                                  : ", ";
549         ++idx;
550 #endif
551         is_reinforced &= dd > 0;
552         is_reinforced &= ds > 0;
553         if (!is_reinforced) {
554             continue;
555         }
556
557         const auto *rf_ptr = &monraces_info[r_idx];
558         if (rf_ptr->kind_flags.has(MonsterKindType::UNIQUE)) {
559             hooked_roff(format("%s%s", _("、", prefix), rf_ptr->name.data()));
560             continue;
561         }
562
563 #ifdef JP
564         hooked_roff(format("、 %dd%d 体の%s", dd, ds, rf_ptr->name.data()));
565 #else
566         auto plural = (dd * ds > 1);
567         GAME_TEXT name[MAX_NLEN];
568         strcpy(name, rf_ptr->name.data());
569         if (plural) {
570             plural_aux(name);
571         }
572         hooked_roff(format("%s%dd%d %s", prefix, dd, ds, name));
573 #endif
574     }
575
576     hooked_roff(_("で成り立っている。", ".  "));
577 }
578
579 void display_monster_collective(lore_type *lore_ptr)
580 {
581     if (lore_ptr->misc_flags.has(MonsterMiscType::ESCORT) || (lore_ptr->flags1 & RF1_ESCORTS) || lore_ptr->reinforce) {
582         hooked_roff(format(_("%s^は通常護衛を伴って現れる。", "%s^ usually appears with escorts.  "), Who::who(lore_ptr->msex)));
583         display_monster_escort_contents(lore_ptr);
584     } else if (lore_ptr->misc_flags.has(MonsterMiscType::HAS_FRIENDS)) {
585         hooked_roff(format(_("%s^は通常集団で現れる。", "%s^ usually appears in groups.  "), Who::who(lore_ptr->msex)));
586     }
587 }
588
589 /*!
590  * @brief モンスターの発射に関する情報を表示するルーチン /
591  * Display monster launching information
592  * @param player_ptr プレイヤーへの参照ポインタ
593  * @param lore_ptr モンスターの思い出構造体への参照ポインタ
594  * @details
595  * This function should only be called when display/dump a recall of
596  * a monster.
597  * @todo max_blows はゲームの中核的なパラメータの1つなのでどこかのヘッダに定数宣言しておきたい
598  */
599 void display_monster_launching(PlayerType *player_ptr, lore_type *lore_ptr)
600 {
601     if (lore_ptr->ability_flags.has(MonsterAbilityType::ROCKET)) {
602         set_damage(player_ptr, lore_ptr, MonsterAbilityType::ROCKET, _("ロケット%sを発射する", "shoot a rocket%s"));
603         lore_ptr->vp[lore_ptr->vn] = lore_ptr->tmp_msg[lore_ptr->vn];
604         lore_ptr->color[lore_ptr->vn++] = TERM_UMBER;
605         lore_ptr->rocket = true;
606     }
607
608     if (lore_ptr->ability_flags.has_not(MonsterAbilityType::SHOOT)) {
609         return;
610     }
611
612     int p = -1; /* Position of SHOOT */
613     int n = 0; /* Number of blows */
614     const int max_blows = 4;
615     for (int m = 0; m < max_blows; m++) {
616         if (lore_ptr->r_ptr->blows[m].method != RaceBlowMethodType::NONE) {
617             n++;
618         } /* Count blows */
619
620         if (lore_ptr->r_ptr->blows[m].method == RaceBlowMethodType::SHOOT) {
621             p = m; /* Remember position */
622             break;
623         }
624     }
625
626     /* When full blows, use a first damage */
627     if (n == max_blows) {
628         p = 0;
629     }
630
631     if (p < 0) {
632         return;
633     }
634
635     if (know_armour(lore_ptr->r_idx, lore_ptr->know_everything)) {
636         strnfmt(lore_ptr->tmp_msg[lore_ptr->vn], sizeof(lore_ptr->tmp_msg[lore_ptr->vn]), _("威力 %dd%d の射撃をする", "fire an arrow (Power:%dd%d)"), lore_ptr->r_ptr->blows[p].d_dice,
637             lore_ptr->r_ptr->blows[p].d_side);
638     } else {
639         angband_strcpy(lore_ptr->tmp_msg[lore_ptr->vn], _("射撃をする", "fire an arrow"), sizeof(lore_ptr->tmp_msg[lore_ptr->vn]));
640     }
641
642     lore_ptr->vp[lore_ptr->vn] = lore_ptr->tmp_msg[lore_ptr->vn];
643     lore_ptr->color[lore_ptr->vn++] = TERM_UMBER;
644     lore_ptr->shoot = true;
645 }
646
647 void display_monster_sometimes(lore_type *lore_ptr)
648 {
649     if (lore_ptr->vn <= 0) {
650         return;
651     }
652
653     hooked_roff(format(_("%s^は", "%s^"), Who::who(lore_ptr->msex)));
654     for (int n = 0; n < lore_ptr->vn; n++) {
655 #ifdef JP
656         if (n != lore_ptr->vn - 1) {
657             const auto verb = conjugate_jverb(lore_ptr->vp[n], JVerbConjugationType::OR);
658             hook_c_roff(lore_ptr->color[n], verb);
659             hook_c_roff(lore_ptr->color[n], "り");
660             hooked_roff("、");
661         } else {
662             hook_c_roff(lore_ptr->color[n], lore_ptr->vp[n]);
663         }
664 #else
665         if (n == 0) {
666             hooked_roff(" may ");
667         } else if (n < lore_ptr->vn - 1) {
668             hooked_roff(", ");
669         } else {
670             hooked_roff(" or ");
671         }
672
673         hook_c_roff(lore_ptr->color[n], lore_ptr->vp[n]);
674 #endif
675     }
676
677     hooked_roff(_("ことがある。", ".  "));
678 }
679
680 void display_monster_guardian(lore_type *lore_ptr)
681 {
682     bool is_kingpin = (lore_ptr->flags1 & RF1_QUESTOR) != 0;
683     is_kingpin &= lore_ptr->r_ptr->r_sights > 0;
684     is_kingpin &= lore_ptr->r_ptr->max_num > 0;
685     is_kingpin &= (lore_ptr->r_idx == MonsterRaceId::OBERON) || (lore_ptr->r_idx == MonsterRaceId::SERPENT);
686     if (is_kingpin) {
687         hook_c_roff(TERM_VIOLET, _("あなたはこのモンスターを殺したいという強い欲望を感じている...", "You feel an intense desire to kill this monster...  "));
688     } else if (lore_ptr->flags7 & RF7_GUARDIAN) {
689         hook_c_roff(TERM_L_RED, _("このモンスターはダンジョンの主である。", "This monster is the master of a dungeon."));
690     }
691
692     hooked_roff("\n");
693 }