OSDN Git Service

[Refactor] #3230 PlayerType::update に関わる処理を、RedrawingFlagsUpdaterに集約した
[hengbandforosx/hengbandosx.git] / src / combat / shoot.cpp
1 #include "combat/shoot.h"
2 #include "artifact/fixed-art-types.h"
3 #include "avatar/avatar.h"
4 #include "combat/attack-criticality.h"
5 #include "core/player-redraw-types.h"
6 #include "core/player-update-types.h"
7 #include "core/stuff-handler.h"
8 #include "effect/attribute-types.h"
9 #include "effect/effect-characteristics.h"
10 #include "effect/effect-processor.h"
11 #include "effect/spells-effect-util.h"
12 #include "flavor/flavor-describer.h"
13 #include "flavor/object-flavor-types.h"
14 #include "floor/cave.h"
15 #include "floor/floor-object.h"
16 #include "floor/geometry.h"
17 #include "game-option/cheat-types.h"
18 #include "game-option/special-options.h"
19 #include "grid/feature-flag-types.h"
20 #include "grid/feature.h"
21 #include "grid/grid.h"
22 #include "inventory/inventory-object.h"
23 #include "inventory/inventory-slot-types.h"
24 #include "io/cursor.h"
25 #include "io/screen-util.h"
26 #include "main/sound-definitions-table.h"
27 #include "main/sound-of-music.h"
28 #include "mind/mind-sniper.h"
29 #include "mind/snipe-types.h"
30 #include "monster-floor/monster-death.h"
31 #include "monster-floor/monster-move.h"
32 #include "monster-race/monster-race.h"
33 #include "monster-race/race-flags-resistance.h"
34 #include "monster-race/race-flags1.h"
35 #include "monster-race/race-flags2.h"
36 #include "monster-race/race-flags3.h"
37 #include "monster-race/race-flags7.h"
38 #include "monster-race/race-indice-types.h"
39 #include "monster-race/race-resistance-mask.h"
40 #include "monster/monster-damage.h"
41 #include "monster/monster-describer.h"
42 #include "monster/monster-info.h"
43 #include "monster/monster-pain-describer.h"
44 #include "monster/monster-status-setter.h"
45 #include "monster/monster-status.h"
46 #include "monster/monster-update.h"
47 #include "object/object-broken.h"
48 #include "object/object-flags.h"
49 #include "object/object-info.h"
50 #include "object/object-mark-types.h"
51 #include "player-base/player-class.h"
52 #include "player-info/class-info.h"
53 #include "player-info/sniper-data-type.h"
54 #include "player-status/player-energy.h"
55 #include "player/player-personality-types.h"
56 #include "player/player-skill.h"
57 #include "player/player-status-table.h"
58 #include "sv-definition/sv-bow-types.h"
59 #include "system/artifact-type-definition.h"
60 #include "system/baseitem-info.h"
61 #include "system/floor-type-definition.h"
62 #include "system/grid-type-definition.h"
63 #include "system/item-entity.h"
64 #include "system/monster-entity.h"
65 #include "system/monster-race-info.h"
66 #include "system/player-type-definition.h"
67 #include "system/redrawing-flags-updater.h"
68 #include "target/projection-path-calculator.h"
69 #include "target/target-checker.h"
70 #include "target/target-getter.h"
71 #include "timed-effect/player-hallucination.h"
72 #include "timed-effect/timed-effects.h"
73 #include "util/bit-flags-calculator.h"
74 #include "view/display-messages.h"
75 #include "wizard/wizard-messages.h"
76 #include "world/world-object.h"
77
78 /*!
79  * @brief 矢弾の属性を定義する
80  * @param bow_ptr 弓のオブジェクト構造体参照ポインタ
81  * @param arrow_ptr 矢弾のオブジェクト構造体参照ポインタ
82  * @return スナイパーの射撃属性、弓矢の属性を考慮する。デフォルトはGF_PLAYER_SHOOT。
83  */
84 AttributeFlags shot_attribute(PlayerType *player_ptr, ItemEntity *bow_ptr, ItemEntity *arrow_ptr, SPELL_IDX snipe_type)
85 {
86     AttributeFlags attribute_flags{};
87     attribute_flags.set(AttributeType::PLAYER_SHOOT);
88
89     TrFlags flags{};
90     auto arrow_flags = object_flags(arrow_ptr);
91     auto bow_flags = object_flags(bow_ptr);
92
93     flags = bow_flags | arrow_flags;
94
95     static const struct snipe_convert_table_t {
96         SPELL_IDX snipe_type;
97         AttributeType attribute;
98     } snipe_convert_table[] = {
99         { SP_LITE, AttributeType::LITE },
100         { SP_FIRE, AttributeType::FIRE },
101         { SP_COLD, AttributeType::COLD },
102         { SP_ELEC, AttributeType::ELEC },
103         { SP_KILL_WALL, AttributeType::KILL_WALL },
104         { SP_EVILNESS, AttributeType::HELL_FIRE },
105         { SP_HOLYNESS, AttributeType::HOLY_FIRE },
106         { SP_FINAL, AttributeType::MANA },
107     };
108
109     static const struct brand_convert_table_t {
110         tr_type brand_type;
111         AttributeType attribute;
112     } brand_convert_table[] = {
113         { TR_BRAND_ACID, AttributeType::ACID },
114         { TR_BRAND_FIRE, AttributeType::FIRE },
115         { TR_BRAND_ELEC, AttributeType::ELEC },
116         { TR_BRAND_COLD, AttributeType::COLD },
117         { TR_BRAND_POIS, AttributeType::POIS },
118         { TR_SLAY_GOOD, AttributeType::HELL_FIRE },
119         { TR_KILL_GOOD, AttributeType::HELL_FIRE },
120         { TR_SLAY_EVIL, AttributeType::HOLY_FIRE },
121         { TR_KILL_EVIL, AttributeType::HOLY_FIRE },
122     };
123
124     for (size_t i = 0; i < sizeof(snipe_convert_table) / sizeof(snipe_convert_table[0]); ++i) {
125         const struct snipe_convert_table_t *p = &snipe_convert_table[i];
126
127         if (snipe_type == p->snipe_type) {
128             attribute_flags.set(p->attribute);
129         }
130     }
131
132     for (size_t i = 0; i < sizeof(brand_convert_table) / sizeof(brand_convert_table[0]); ++i) {
133         const struct brand_convert_table_t *p = &brand_convert_table[i];
134
135         if (flags.has(p->brand_type)) {
136             attribute_flags.set(p->attribute);
137         }
138     }
139
140     if ((flags.has(TR_FORCE_WEAPON)) && (player_ptr->csp > (player_ptr->msp / 30))) {
141         attribute_flags.set(AttributeType::MANA);
142     }
143
144     return attribute_flags;
145 }
146
147 /*!
148  * @brief 矢弾を射撃した際のスレイ倍率をかけた結果を返す /
149  * Determines the odds of an object breaking when thrown at a monster
150  * @param bow_ptr 弓のオブジェクト構造体参照ポインタ
151  * @param arrow_ptr 矢弾のオブジェクト構造体参照ポインタ
152  * @param tdam 計算途中のダメージ量
153  * @param monster_ptr 目標モンスターの構造体参照ポインタ
154  * @return スレイ倍率をかけたダメージ量
155  */
156 static MULTIPLY calc_shot_damage_with_slay(
157     PlayerType *player_ptr, ItemEntity *bow_ptr, ItemEntity *arrow_ptr, int tdam, MonsterEntity *monster_ptr, SPELL_IDX snipe_type)
158 {
159     MULTIPLY mult = 10;
160
161     MonsterRaceInfo *race_ptr = &monraces_info[monster_ptr->r_idx];
162
163     TrFlags flags{};
164     auto arrow_flags = object_flags(arrow_ptr);
165     auto bow_flags = object_flags(bow_ptr);
166
167     flags = bow_flags | arrow_flags;
168
169     /* Some "weapons" and "ammo" do extra damage */
170     switch (arrow_ptr->bi_key.tval()) {
171     case ItemKindType::SHOT:
172     case ItemKindType::ARROW:
173     case ItemKindType::BOLT: {
174         if ((flags.has(TR_SLAY_ANIMAL)) && race_ptr->kind_flags.has(MonsterKindType::ANIMAL)) {
175             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
176                 race_ptr->r_kind_flags.set(MonsterKindType::ANIMAL);
177             }
178             if (mult < 17) {
179                 mult = 17;
180             }
181         }
182
183         if ((flags.has(TR_KILL_ANIMAL)) && race_ptr->kind_flags.has(MonsterKindType::ANIMAL)) {
184             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
185                 race_ptr->r_kind_flags.set(MonsterKindType::ANIMAL);
186             }
187             if (mult < 27) {
188                 mult = 27;
189             }
190         }
191
192         if ((flags.has(TR_SLAY_EVIL)) && race_ptr->kind_flags.has(MonsterKindType::EVIL)) {
193             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
194                 race_ptr->r_kind_flags.set(MonsterKindType::EVIL);
195             }
196             if (mult < 15) {
197                 mult = 15;
198             }
199         }
200
201         if ((flags.has(TR_KILL_EVIL)) && race_ptr->kind_flags.has(MonsterKindType::EVIL)) {
202             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
203                 race_ptr->r_kind_flags.set(MonsterKindType::EVIL);
204             }
205             if (mult < 25) {
206                 mult = 25;
207             }
208         }
209
210         if ((flags.has(TR_SLAY_GOOD)) && race_ptr->kind_flags.has(MonsterKindType::GOOD)) {
211             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
212                 race_ptr->r_kind_flags.set(MonsterKindType::GOOD);
213             }
214             if (mult < 15) {
215                 mult = 15;
216             }
217         }
218
219         if ((flags.has(TR_KILL_GOOD)) && race_ptr->kind_flags.has(MonsterKindType::GOOD)) {
220             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
221                 race_ptr->r_kind_flags.set(MonsterKindType::GOOD);
222             }
223             if (mult < 25) {
224                 mult = 25;
225             }
226         }
227
228         if ((flags.has(TR_SLAY_HUMAN)) && race_ptr->kind_flags.has(MonsterKindType::HUMAN)) {
229             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
230                 race_ptr->r_kind_flags.set(MonsterKindType::HUMAN);
231             }
232             if (mult < 17) {
233                 mult = 17;
234             }
235         }
236
237         if ((flags.has(TR_KILL_HUMAN)) && race_ptr->kind_flags.has(MonsterKindType::HUMAN)) {
238             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
239                 race_ptr->r_kind_flags.set(MonsterKindType::HUMAN);
240             }
241             if (mult < 27) {
242                 mult = 27;
243             }
244         }
245
246         if ((flags.has(TR_SLAY_UNDEAD)) && race_ptr->kind_flags.has(MonsterKindType::UNDEAD)) {
247             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
248                 race_ptr->r_kind_flags.set(MonsterKindType::UNDEAD);
249             }
250             if (mult < 20) {
251                 mult = 20;
252             }
253         }
254
255         if ((flags.has(TR_KILL_UNDEAD)) && race_ptr->kind_flags.has(MonsterKindType::UNDEAD)) {
256             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
257                 race_ptr->r_kind_flags.set(MonsterKindType::UNDEAD);
258             }
259             if (mult < 30) {
260                 mult = 30;
261             }
262         }
263
264         if ((flags.has(TR_SLAY_DEMON)) && race_ptr->kind_flags.has(MonsterKindType::DEMON)) {
265             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
266                 race_ptr->r_kind_flags.set(MonsterKindType::DEMON);
267             }
268             if (mult < 20) {
269                 mult = 20;
270             }
271         }
272
273         if ((flags.has(TR_KILL_DEMON)) && race_ptr->kind_flags.has(MonsterKindType::DEMON)) {
274             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
275                 race_ptr->r_kind_flags.set(MonsterKindType::DEMON);
276             }
277             if (mult < 30) {
278                 mult = 30;
279             }
280         }
281
282         if ((flags.has(TR_SLAY_ORC)) && race_ptr->kind_flags.has(MonsterKindType::ORC)) {
283             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
284                 race_ptr->r_kind_flags.set(MonsterKindType::ORC);
285             }
286             if (mult < 20) {
287                 mult = 20;
288             }
289         }
290
291         if ((flags.has(TR_KILL_ORC)) && race_ptr->kind_flags.has(MonsterKindType::ORC)) {
292             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
293                 race_ptr->r_kind_flags.set(MonsterKindType::ORC);
294             }
295             if (mult < 30) {
296                 mult = 30;
297             }
298         }
299
300         if ((flags.has(TR_SLAY_TROLL)) && race_ptr->kind_flags.has(MonsterKindType::TROLL)) {
301             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
302                 race_ptr->r_kind_flags.set(MonsterKindType::TROLL);
303             }
304
305             if (mult < 20) {
306                 mult = 20;
307             }
308         }
309
310         if ((flags.has(TR_KILL_TROLL)) && race_ptr->kind_flags.has(MonsterKindType::TROLL)) {
311             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
312                 race_ptr->r_kind_flags.set(MonsterKindType::TROLL);
313             }
314             if (mult < 30) {
315                 mult = 30;
316             }
317         }
318
319         if ((flags.has(TR_SLAY_GIANT)) && race_ptr->kind_flags.has(MonsterKindType::GIANT)) {
320             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
321                 race_ptr->r_kind_flags.set(MonsterKindType::GIANT);
322             }
323             if (mult < 20) {
324                 mult = 20;
325             }
326         }
327
328         if ((flags.has(TR_KILL_GIANT)) && race_ptr->kind_flags.has(MonsterKindType::GIANT)) {
329             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
330                 race_ptr->r_kind_flags.set(MonsterKindType::GIANT);
331             }
332             if (mult < 30) {
333                 mult = 30;
334             }
335         }
336
337         if ((flags.has(TR_SLAY_DRAGON)) && race_ptr->kind_flags.has(MonsterKindType::DRAGON)) {
338             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
339                 race_ptr->r_kind_flags.set(MonsterKindType::DRAGON);
340             }
341             if (mult < 20) {
342                 mult = 20;
343             }
344         }
345
346         if ((flags.has(TR_KILL_DRAGON)) && race_ptr->kind_flags.has(MonsterKindType::DRAGON)) {
347             if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
348                 race_ptr->r_kind_flags.set(MonsterKindType::DRAGON);
349             }
350             if (mult < 30) {
351                 mult = 30;
352             }
353
354             auto can_eliminate_smaug = arrow_ptr->is_specific_artifact(FixedArtifactId::BARD_ARROW);
355             can_eliminate_smaug &= player_ptr->inventory_list[INVEN_BOW].is_specific_artifact(FixedArtifactId::BARD);
356             can_eliminate_smaug &= monster_ptr->r_idx == MonsterRaceId::SMAUG;
357             if (can_eliminate_smaug) {
358                 mult *= 5;
359             }
360         }
361
362         if (flags.has(TR_BRAND_ACID)) {
363             /* Notice immunity */
364             if (race_ptr->resistance_flags.has_any_of(RFR_EFF_IM_ACID_MASK)) {
365                 if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
366                     race_ptr->r_resistance_flags.set(race_ptr->resistance_flags & RFR_EFF_IM_ACID_MASK);
367                 }
368             } else {
369                 if (mult < 17) {
370                     mult = 17;
371                 }
372             }
373         }
374
375         if (flags.has(TR_BRAND_ELEC)) {
376             /* Notice immunity */
377             if (race_ptr->resistance_flags.has_any_of(RFR_EFF_IM_ELEC_MASK)) {
378                 if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
379                     race_ptr->r_resistance_flags.set(race_ptr->resistance_flags & RFR_EFF_IM_ELEC_MASK);
380                 }
381             } else {
382                 if (mult < 17) {
383                     mult = 17;
384                 }
385             }
386         }
387
388         if (flags.has(TR_BRAND_FIRE)) {
389             /* Notice immunity */
390             if (race_ptr->resistance_flags.has_any_of(RFR_EFF_IM_FIRE_MASK)) {
391                 if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
392                     race_ptr->r_resistance_flags.set(race_ptr->resistance_flags & RFR_EFF_IM_FIRE_MASK);
393                 }
394             }
395             /* Otherwise, take the damage */
396             else {
397                 if (race_ptr->resistance_flags.has(MonsterResistanceType::HURT_FIRE)) {
398                     if (mult < 25) {
399                         mult = 25;
400                     }
401                     if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
402                         race_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_FIRE);
403                     }
404                 } else if (mult < 17) {
405                     mult = 17;
406                 }
407             }
408         }
409
410         if (flags.has(TR_BRAND_COLD)) {
411             /* Notice immunity */
412             if (race_ptr->resistance_flags.has_any_of(RFR_EFF_IM_COLD_MASK)) {
413                 if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
414                     race_ptr->r_resistance_flags.set(race_ptr->resistance_flags & RFR_EFF_IM_COLD_MASK);
415                 }
416             }
417             /* Otherwise, take the damage */
418             else {
419                 if (race_ptr->resistance_flags.has(MonsterResistanceType::HURT_COLD)) {
420                     if (mult < 25) {
421                         mult = 25;
422                     }
423                     if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
424                         race_ptr->r_resistance_flags.set(MonsterResistanceType::HURT_COLD);
425                     }
426                 } else if (mult < 17) {
427                     mult = 17;
428                 }
429             }
430         }
431
432         if (flags.has(TR_BRAND_POIS)) {
433             /* Notice immunity */
434             if (race_ptr->resistance_flags.has_any_of(RFR_EFF_IM_POISON_MASK)) {
435                 if (is_original_ap_and_seen(player_ptr, monster_ptr)) {
436                     race_ptr->r_resistance_flags.set(race_ptr->resistance_flags & RFR_EFF_IM_POISON_MASK);
437                 }
438             }
439             /* Otherwise, take the damage */
440             else {
441                 if (mult < 17) {
442                     mult = 17;
443                 }
444             }
445         }
446
447         if ((flags.has(TR_FORCE_WEAPON)) && (player_ptr->csp > (player_ptr->msp / 30))) {
448             player_ptr->csp -= (1 + (player_ptr->msp / 30));
449             set_bits(player_ptr->redraw, PR_MP);
450             mult = mult * 5 / 2;
451         }
452         break;
453     }
454
455     default:
456         break;
457     }
458
459     /* Sniper */
460     if (snipe_type) {
461         mult = calc_snipe_damage_with_slay(player_ptr, mult, monster_ptr, snipe_type);
462     }
463
464     /* Return the total damage */
465     return tdam * mult / 10;
466 }
467
468 /*!
469  * @brief 射撃処理実行 /
470  * Fire an object from the pack or floor.
471  * @param item 射撃するオブジェクトの所持ID
472  * @param bow_ptr 射撃武器のオブジェクト参照ポインタ
473  * @details
474  * <pre>
475  * You may only fire items that "match" your missile launcher.
476  * You must use slings + pebbles/shots, bows + arrows, xbows + bolts.
477  * See "calc_bonuses()" for more calculations and such.
478  * Note that "firing" a missile is MUCH better than "throwing" it.
479  * Note: "unseen" monsters are very hard to hit.
480  * Objects are more likely to break if they "attempt" to hit a monster.
481  * Rangers (with Bows) and Anyone (with "Extra Shots") get extra shots.
482  * The "extra shot" code works by decreasing the amount of energy
483  * required to make each shot, spreading the shots out over time.
484  * Note that when firing missiles, the launcher multiplier is applied
485  * after all the bonuses are added in, making multipliers very useful.
486  * Note that Bows of "Extra Might" get extra range and an extra bonus
487  * for the damage multiplier.
488  * Note that Bows of "Extra Shots" give an extra shot.
489  * </pre>
490  */
491 void exe_fire(PlayerType *player_ptr, INVENTORY_IDX item, ItemEntity *j_ptr, SPELL_IDX snipe_type)
492 {
493     POSITION y, x, ny, nx, ty, tx, prev_y, prev_x;
494     ItemEntity forge;
495     ItemEntity *q_ptr;
496     ItemEntity *o_ptr;
497
498     AttributeFlags attribute_flags{};
499     attribute_flags.set(AttributeType::PLAYER_SHOOT);
500
501     auto hit_body = false;
502     auto stick_to = false;
503
504     /* Access the item (if in the pack) */
505     auto *floor_ptr = player_ptr->current_floor_ptr;
506     if (item >= 0) {
507         o_ptr = &player_ptr->inventory_list[item];
508     } else {
509         o_ptr = &floor_ptr->o_list[0 - item];
510     }
511
512     /* Sniper - Cannot shot a single arrow twice */
513     if ((snipe_type == SP_DOUBLE) && (o_ptr->number < 2)) {
514         snipe_type = SP_NONE;
515     }
516
517     const auto item_name = describe_flavor(player_ptr, o_ptr, OD_OMIT_PREFIX);
518
519     /* Use the proper number of shots */
520     auto thits = player_ptr->num_fire;
521
522     /* Use a base distance */
523     auto tdis = 10;
524
525     /* Base damage from thrown object plus launcher bonus */
526     auto tdam_base = damroll(o_ptr->dd, o_ptr->ds) + o_ptr->to_d + j_ptr->to_d;
527
528     /* Actually "fire" the object */
529     const auto tval = j_ptr->bi_key.tval();
530     const auto median_skill_exp = PlayerSkill::weapon_exp_at(PlayerSkillRank::MASTER) / 2;
531     const auto bonus = (player_ptr->to_h_b + o_ptr->to_h + j_ptr->to_h);
532     const auto &weapon_exps = player_ptr->weapon_exp[tval];
533     constexpr auto bow_magnification = 200;
534     constexpr auto xbow_magnification = 400;
535     int chance;
536     if (tval == ItemKindType::NONE) {
537         chance = (player_ptr->skill_thb + ((weapon_exps[0] - median_skill_exp) / bow_magnification + bonus) * BTH_PLUS_ADJ);
538     } else {
539         const auto sval = j_ptr->bi_key.sval().value();
540         if (j_ptr->is_cross_bow()) {
541             chance = (player_ptr->skill_thb + (weapon_exps[sval] / xbow_magnification + bonus) * BTH_PLUS_ADJ);
542         } else {
543             chance = (player_ptr->skill_thb + ((weapon_exps[sval] - median_skill_exp) / bow_magnification + bonus) * BTH_PLUS_ADJ);
544         }
545     }
546
547     PlayerEnergy(player_ptr).set_player_turn_energy(j_ptr->get_bow_energy());
548     auto tmul = j_ptr->get_arrow_magnification();
549
550     /* Get extra "power" from "extra might" */
551     if (player_ptr->xtra_might) {
552         tmul++;
553     }
554
555     tmul = tmul * (100 + (int)(adj_str_td[player_ptr->stat_index[A_STR]]) - 128);
556
557     /* Boost the damage */
558     tdam_base *= tmul;
559     tdam_base /= 100;
560
561     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
562     auto sniper_concent = sniper_data ? sniper_data->concent : 0;
563
564     /* Base range */
565     tdis = 13 + tmul / 80;
566     if (j_ptr->is_cross_bow()) {
567         tdis -= (5 - (sniper_concent + 1) / 2);
568     }
569
570     project_length = tdis + 1;
571
572     /* Get a direction (or cancel) */
573     DIRECTION dir;
574     if (!get_aim_dir(player_ptr, &dir)) {
575         PlayerEnergy(player_ptr).reset_player_turn();
576
577         if (snipe_type == SP_AWAY) {
578             snipe_type = SP_NONE;
579         }
580
581         /* need not to reset project_length (already did)*/
582
583         return;
584     }
585
586     if (snipe_type != SP_NONE) {
587         sound(SOUND_ZAP);
588     }
589
590     /* Predict the "target" location */
591     tx = player_ptr->x + 99 * ddx[dir];
592     ty = player_ptr->y + 99 * ddy[dir];
593
594     /* Check for "target request" */
595     if ((dir == 5) && target_okay(player_ptr)) {
596         tx = target_col;
597         ty = target_row;
598     }
599
600     /* Get projection path length */
601     projection_path path_g(player_ptr, project_length, player_ptr->y, player_ptr->x, ty, tx, PROJECT_PATH | PROJECT_THRU);
602     tdis = path_g.path_num() - 1;
603
604     project_length = 0; /* reset to default */
605
606     /* Don't shoot at my feet */
607     if (tx == player_ptr->x && ty == player_ptr->y) {
608         PlayerEnergy(player_ptr).reset_player_turn();
609
610         /* project_length is already reset to 0 */
611
612         return;
613     }
614
615     /* Take a (partial) turn */
616     PlayerEnergy(player_ptr).div_player_turn_energy(thits);
617     player_ptr->is_fired = true;
618
619     /* Sniper - Difficult to shot twice at 1 turn */
620     if (snipe_type == SP_DOUBLE) {
621         sniper_concent = (sniper_concent + 1) / 2;
622     }
623
624     /* Sniper - Repeat shooting when double shots */
625     for (auto i = 0; i < ((snipe_type == SP_DOUBLE) ? 2 : 1); i++) {
626         /* Start at the player */
627         y = player_ptr->y;
628         x = player_ptr->x;
629         q_ptr = &forge;
630         q_ptr->copy_from(o_ptr);
631
632         /* Single object */
633         q_ptr->number = 1;
634
635         vary_item(player_ptr, item, -1);
636
637         sound(SOUND_SHOOT);
638         handle_stuff(player_ptr);
639
640         prev_y = y;
641         prev_x = x;
642
643         /* The shot does not hit yet */
644         hit_body = false;
645
646         /* Travel until stopped */
647         for (auto cur_dis = 0; cur_dis <= tdis;) {
648             grid_type *g_ptr;
649
650             /* Hack -- Stop at the target */
651             if ((y == ty) && (x == tx)) {
652                 break;
653             }
654
655             /* Calculate the new location (see "project()") */
656             ny = y;
657             nx = x;
658             mmove2(&ny, &nx, player_ptr->y, player_ptr->x, ty, tx);
659
660             /* Shatter Arrow */
661             if (snipe_type == SP_KILL_WALL) {
662                 g_ptr = &floor_ptr->grid_array[ny][nx];
663
664                 if (g_ptr->cave_has_flag(TerrainCharacteristics::HURT_ROCK) && !g_ptr->m_idx) {
665                     if (any_bits(g_ptr->info, (CAVE_MARK))) {
666                         msg_print(_("岩が砕け散った。", "Wall rocks were shattered."));
667                     }
668                     /* Forget the wall */
669                     reset_bits(g_ptr->info, (CAVE_MARK));
670                     const auto flags = {
671                         StatusRedrawingFlag::VIEW,
672                         StatusRedrawingFlag::LITE,
673                         StatusRedrawingFlag::FLOW,
674                         StatusRedrawingFlag::MONSTER_LITE,
675                     };
676                     RedrawingFlagsUpdater::get_instance().set_flags(flags);
677
678                     /* Destroy the wall */
679                     cave_alter_feat(player_ptr, ny, nx, TerrainCharacteristics::HURT_ROCK);
680
681                     hit_body = true;
682                     break;
683                 }
684             }
685
686             /* Stopped by walls/doors */
687             if (!cave_has_flag_bold(floor_ptr, ny, nx, TerrainCharacteristics::PROJECT) && !floor_ptr->grid_array[ny][nx].m_idx) {
688                 break;
689             }
690
691             /* Advance the distance */
692             cur_dis++;
693
694             /* Sniper */
695             if (snipe_type == SP_LITE) {
696                 set_bits(floor_ptr->grid_array[ny][nx].info, CAVE_GLOW);
697                 note_spot(player_ptr, ny, nx);
698                 lite_spot(player_ptr, ny, nx);
699             }
700
701             /* The player can see the (on screen) missile */
702             if (panel_contains(ny, nx) && player_can_see_bold(player_ptr, ny, nx)) {
703                 const auto a = q_ptr->get_color();
704                 const auto c = q_ptr->get_symbol();
705
706                 /* Draw, Hilite, Fresh, Pause, Erase */
707                 if (delay_factor > 0) {
708                     print_rel(player_ptr, c, a, ny, nx);
709                     move_cursor_relative(ny, nx);
710                     term_fresh();
711                     term_xtra(TERM_XTRA_DELAY, delay_factor);
712                     lite_spot(player_ptr, ny, nx);
713                     term_fresh();
714                 }
715             }
716
717             /* The player cannot see the missile */
718             else {
719                 /* Pause anyway, for consistancy **/
720                 if (delay_factor > 0) {
721                     term_xtra(TERM_XTRA_DELAY, delay_factor);
722                 }
723             }
724
725             /* Sniper */
726             if (snipe_type == SP_KILL_TRAP) {
727                 constexpr auto flags = PROJECT_JUMP | PROJECT_HIDE | PROJECT_GRID | PROJECT_ITEM;
728                 project(player_ptr, 0, 0, ny, nx, 0, AttributeType::KILL_TRAP, flags);
729             }
730
731             /* Sniper */
732             if (snipe_type == SP_EVILNESS) {
733                 reset_bits(floor_ptr->grid_array[ny][nx].info, (CAVE_GLOW | CAVE_MARK));
734                 note_spot(player_ptr, ny, nx);
735                 lite_spot(player_ptr, ny, nx);
736             }
737
738             prev_y = y;
739             prev_x = x;
740
741             /* Save the new location */
742             x = nx;
743             y = ny;
744
745             /* Monster here, Try to hit it */
746             if (floor_ptr->grid_array[y][x].m_idx) {
747                 sound(SOUND_SHOOT_HIT);
748                 grid_type *c_mon_ptr = &floor_ptr->grid_array[y][x];
749
750                 auto *m_ptr = &floor_ptr->m_list[c_mon_ptr->m_idx];
751                 auto *r_ptr = &monraces_info[m_ptr->r_idx];
752
753                 /* Check the visibility */
754                 auto visible = m_ptr->ml;
755
756                 /* Note the collision */
757                 hit_body = true;
758
759                 if (m_ptr->is_asleep()) {
760                     if (r_ptr->kind_flags.has_not(MonsterKindType::EVIL) || one_in_(5)) {
761                         chg_virtue(player_ptr, Virtue::COMPASSION, -1);
762                     }
763                     if (r_ptr->kind_flags.has_not(MonsterKindType::EVIL) || one_in_(5)) {
764                         chg_virtue(player_ptr, Virtue::HONOUR, -1);
765                     }
766                 }
767
768                 if ((r_ptr->level + 10) > player_ptr->lev) {
769                     PlayerSkill(player_ptr).gain_range_weapon_exp(j_ptr);
770                 }
771
772                 if (player_ptr->riding) {
773                     PlayerSkill(player_ptr).gain_riding_skill_exp_on_range_attack();
774                 }
775
776                 /* Did we hit it (penalize range) */
777                 if (test_hit_fire(player_ptr, chance - cur_dis, m_ptr, m_ptr->ml, item_name.data())) {
778                     bool fear = false;
779                     auto tdam = tdam_base; //!< @note 実際に与えるダメージ
780                     auto base_dam = tdam; //!< @note 補正前の与えるダメージ(無傷、全ての耐性など)
781
782                     /* Get extra damage from concentration */
783                     tdam = boost_concentration_damage(player_ptr, tdam);
784
785                     /* Handle unseen monster */
786                     if (!visible) {
787                         /* Invisible monster */
788                         msg_format(_("%sが敵を捕捉した。", "The %s finds a mark."), item_name.data());
789                     }
790
791                     /* Handle visible monster */
792                     else {
793                         /* Get "the monster" or "it" */
794                         const auto m_name = monster_desc(player_ptr, m_ptr, 0);
795
796                         msg_format(_("%sが%sに命中した。", "The %s hits %s."), item_name.data(), m_name.data());
797
798                         if (m_ptr->ml) {
799                             if (!player_ptr->effects()->hallucination()->is_hallucinated()) {
800                                 monster_race_track(player_ptr, m_ptr->ap_r_idx);
801                             }
802
803                             health_track(player_ptr, c_mon_ptr->m_idx);
804                         }
805                     }
806
807                     if (snipe_type == SP_NEEDLE) {
808                         const auto is_unique = r_ptr->kind_flags.has(MonsterKindType::UNIQUE);
809                         const auto fatality = randint1(r_ptr->level / (3 + sniper_concent)) + (8 - sniper_concent);
810                         if ((randint1(fatality) == 1) && !is_unique && none_bits(r_ptr->flags7, RF7_UNIQUE2)) {
811                             /* Get "the monster" or "it" */
812                             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
813
814                             tdam = m_ptr->hp + 1;
815                             base_dam = tdam;
816                             msg_format(_("%sの急所に突き刺さった!", "Your shot hit a fatal spot of %s!"), m_name.data());
817                         } else {
818                             tdam = 1;
819                             base_dam = tdam;
820                         }
821                     } else {
822
823                         attribute_flags = shot_attribute(player_ptr, j_ptr, q_ptr, snipe_type);
824                         /* Apply special damage */
825                         tdam = calc_shot_damage_with_slay(player_ptr, j_ptr, q_ptr, tdam, m_ptr, snipe_type);
826                         tdam = critical_shot(player_ptr, q_ptr->weight, q_ptr->to_h, j_ptr->to_h, tdam);
827
828                         /* No negative damage */
829                         if (tdam < 0) {
830                             tdam = 0;
831                         }
832
833                         /* Modify the damage */
834                         base_dam = tdam;
835                         tdam = mon_damage_mod(player_ptr, m_ptr, tdam, false);
836                     }
837
838                     msg_format_wizard(player_ptr, CHEAT_MONSTER, _("%dのダメージを与えた。(残りHP %d/%d(%d))", "You do %d damage. (left HP %d/%d(%d))"), tdam,
839                         m_ptr->hp - tdam, m_ptr->maxhp, m_ptr->max_maxhp);
840
841                     /* Sniper */
842                     if (snipe_type == SP_EXPLODE) {
843                         uint16_t flg = (PROJECT_STOP | PROJECT_JUMP | PROJECT_KILL | PROJECT_GRID);
844
845                         sound(SOUND_EXPLODE); /* No explode sound - use breath fire instead */
846                         project(player_ptr, 0, ((sniper_concent + 1) / 2 + 1), ny, nx, base_dam, AttributeType::MISSILE, flg);
847                         break;
848                     }
849
850                     /* Sniper */
851                     if (snipe_type == SP_HOLYNESS) {
852                         set_bits(floor_ptr->grid_array[ny][nx].info, CAVE_GLOW);
853                         note_spot(player_ptr, ny, nx);
854                         lite_spot(player_ptr, ny, nx);
855                     }
856
857                     /* Hit the monster, check for death */
858                     MonsterDamageProcessor mdp(player_ptr, c_mon_ptr->m_idx, tdam, &fear, attribute_flags);
859                     if (mdp.mon_take_hit(extract_note_dies(m_ptr->get_real_r_idx()))) {
860                         /* Dead monster */
861                     }
862
863                     /* No death */
864                     else {
865                         /* STICK TO */
866                         if (q_ptr->is_fixed_artifact() && (sniper_concent == 0)) {
867                             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
868
869                             stick_to = true;
870                             msg_format(_("%sは%sに突き刺さった!", "%s^ is stuck in %s!"), item_name.data(), m_name.data());
871                         }
872
873                         if (const auto pain_message = MonsterPainDescriber(player_ptr, c_mon_ptr->m_idx).describe(tdam);
874                             !pain_message.empty()) {
875                             msg_print(pain_message);
876                         }
877
878                         /* Anger the monster */
879                         if (tdam > 0) {
880                             anger_monster(player_ptr, m_ptr);
881                         }
882
883                         if (fear && m_ptr->ml) {
884                             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
885                             sound(SOUND_FLEE);
886                             msg_format(_("%s^は恐怖して逃げ出した!", "%s^ flees in terror!"), m_name.data());
887                         }
888
889                         set_target(m_ptr, player_ptr->y, player_ptr->x);
890
891                         /* Sniper */
892                         if (snipe_type == SP_RUSH) {
893                             int n = randint1(5) + 3;
894                             MONSTER_IDX m_idx = c_mon_ptr->m_idx;
895
896                             for (; cur_dis <= tdis;) {
897                                 POSITION ox = nx;
898                                 POSITION oy = ny;
899
900                                 if (!n) {
901                                     break;
902                                 }
903
904                                 /* Calculate the new location (see "project()") */
905                                 mmove2(&ny, &nx, player_ptr->y, player_ptr->x, ty, tx);
906
907                                 /* Stopped by wilderness boundary */
908                                 if (!in_bounds2(floor_ptr, ny, nx)) {
909                                     break;
910                                 }
911
912                                 /* Stopped by walls/doors */
913                                 if (!player_can_enter(player_ptr, floor_ptr->grid_array[ny][nx].feat, 0)) {
914                                     break;
915                                 }
916
917                                 /* Stopped by monsters */
918                                 if (!is_cave_empty_bold(player_ptr, ny, nx)) {
919                                     break;
920                                 }
921
922                                 floor_ptr->grid_array[ny][nx].m_idx = m_idx;
923                                 floor_ptr->grid_array[oy][ox].m_idx = 0;
924
925                                 m_ptr->fx = nx;
926                                 m_ptr->fy = ny;
927
928                                 update_monster(player_ptr, m_idx, true);
929
930                                 if (delay_factor > 0) {
931                                     lite_spot(player_ptr, ny, nx);
932                                     lite_spot(player_ptr, oy, ox);
933                                     term_fresh();
934                                     term_xtra(TERM_XTRA_DELAY, delay_factor);
935                                 }
936
937                                 x = nx;
938                                 y = ny;
939                                 cur_dis++;
940                                 n--;
941                             }
942                         }
943                     }
944                 }
945
946                 /* Sniper */
947                 if (snipe_type == SP_PIERCE && sniper_concent > 0) {
948                     sniper_concent--;
949                     continue;
950                 }
951
952                 /* Stop looking */
953                 break;
954             }
955         }
956
957         /* Chance of breakage (during attacks) */
958         auto j = (hit_body ? breakage_chance(player_ptr, q_ptr, PlayerClass(player_ptr).equals(PlayerClassType::ARCHER), snipe_type) : 0);
959
960         if (stick_to) {
961             MONSTER_IDX m_idx = floor_ptr->grid_array[y][x].m_idx;
962             auto *m_ptr = &floor_ptr->m_list[m_idx];
963             OBJECT_IDX o_idx = o_pop(floor_ptr);
964
965             if (!o_idx) {
966                 msg_format(_("%sはどこかへ行った。", "The %s went somewhere."), item_name.data());
967                 if (q_ptr->is_fixed_artifact()) {
968                     ArtifactsInfo::get_instance().get_artifact(j_ptr->fixed_artifact_idx).is_generated = false;
969                 }
970                 return;
971             }
972
973             o_ptr = &floor_ptr->o_list[o_idx];
974             o_ptr->copy_from(q_ptr);
975
976             /* Forget mark */
977             o_ptr->marked.reset(OmType::TOUCHED);
978
979             /* Forget location */
980             o_ptr->iy = o_ptr->ix = 0;
981
982             /* Memorize monster */
983             o_ptr->held_m_idx = m_idx;
984
985             /* Carry object */
986             m_ptr->hold_o_idx_list.add(floor_ptr, o_idx);
987         } else if (cave_has_flag_bold(floor_ptr, y, x, TerrainCharacteristics::PROJECT)) {
988             /* Drop (or break) near that location */
989             (void)drop_near(player_ptr, q_ptr, j, y, x);
990         } else {
991             /* Drop (or break) near that location */
992             (void)drop_near(player_ptr, q_ptr, j, prev_y, prev_x);
993         }
994
995         /* Sniper - Repeat shooting when double shots */
996     }
997
998     /* Sniper - Loose his/her concentration after any shot */
999     reset_concentration(player_ptr, false);
1000 }
1001
1002 /*!
1003  * @brief プレイヤーからモンスターへの射撃命中判定
1004  * @param chance 基本命中値
1005  * @param monster_ptr モンスターの構造体参照ポインタ
1006  * @param vis 目標を視界に捕らえているならばTRUEを指定
1007  * @param item_name 石川五右衛門専用メッセージ:無効化した矢弾の名前
1008  * @return 命中と判定された場合TRUEを返す
1009  * @note 最低命中率5%、最大命中率95%
1010  */
1011 bool test_hit_fire(PlayerType *player_ptr, int chance, MonsterEntity *m_ptr, int vis, std::string_view item_name)
1012 {
1013     int k;
1014     ARMOUR_CLASS ac;
1015     auto *r_ptr = &monraces_info[m_ptr->r_idx];
1016
1017     /* Percentile dice */
1018     k = randint1(100);
1019
1020     auto sniper_data = PlayerClass(player_ptr).get_specific_data<sniper_data_type>();
1021     auto sniper_concent = sniper_data ? sniper_data->concent : 0;
1022
1023     /* Snipers with high-concentration reduce instant miss percentage.*/
1024     k += sniper_concent;
1025
1026     /* Hack -- Instant miss or hit */
1027     if (k <= 5) {
1028         return false;
1029     }
1030     if (k > 95) {
1031         return true;
1032     }
1033
1034     if (player_ptr->ppersonality == PERSONALITY_LAZY) {
1035         if (one_in_(20)) {
1036             return false;
1037         }
1038     }
1039
1040     /* Never hit */
1041     if (chance <= 0) {
1042         return false;
1043     }
1044
1045     ac = r_ptr->ac;
1046     ac = ac * (8 - sniper_concent) / 8;
1047
1048     if (m_ptr->r_idx == MonsterRaceId::GOEMON && !m_ptr->is_asleep()) {
1049         ac *= 3;
1050     }
1051
1052     /* Invisible monsters are harder to hit */
1053     if (!vis) {
1054         chance = (chance + 1) / 2;
1055     }
1056
1057     /* Power competes against armor */
1058     if (randint0(chance) < (ac * 3 / 4)) {
1059         if (m_ptr->r_idx == MonsterRaceId::GOEMON && !m_ptr->is_asleep()) {
1060             const auto m_name = monster_desc(player_ptr, m_ptr, 0);
1061             msg_format(_("%sは%sを斬り捨てた!", "%s cuts down %s!"), m_name.data(), item_name.data());
1062         }
1063         return false;
1064     }
1065
1066     /* Assume hit */
1067     return true;
1068 }
1069
1070 /*!
1071  * @brief プレイヤーからモンスターへの射撃クリティカル判定
1072  * @param weight 矢弾の重量
1073  * @param plus_ammo 矢弾の命中修正
1074  * @param plus_bow 弓の命中修正
1075  * @param dam 現在算出中のダメージ値
1076  * @return クリティカル修正が入ったダメージ値
1077  */
1078 int critical_shot(PlayerType *player_ptr, WEIGHT weight, int plus_ammo, int plus_bow, int dam)
1079 {
1080     const auto &item = player_ptr->inventory_list[INVEN_BOW];
1081     const auto bonus = player_ptr->to_h_b + plus_ammo;
1082     const auto tval = item.bi_key.tval();
1083     const auto median_skill_exp = PlayerSkill::weapon_exp_at(PlayerSkillRank::MASTER) / 2;
1084     const auto &weapon_exps = player_ptr->weapon_exp[tval];
1085     constexpr auto bow_magnification = 200;
1086     constexpr auto xbow_magnification = 400;
1087     int power;
1088     if (tval == ItemKindType::NONE) {
1089         power = player_ptr->skill_thb + ((weapon_exps[0] - median_skill_exp) / bow_magnification + bonus) * BTH_PLUS_ADJ;
1090     } else {
1091         const auto sval = item.bi_key.sval().value();
1092         const auto weapon_exp = weapon_exps[sval];
1093         if (player_ptr->tval_ammo == ItemKindType::BOLT) {
1094             power = (player_ptr->skill_thb + (weapon_exp / xbow_magnification + bonus) * BTH_PLUS_ADJ);
1095         } else {
1096             power = player_ptr->skill_thb + ((weapon_exp - median_skill_exp) / bow_magnification + bonus) * BTH_PLUS_ADJ;
1097         }
1098     }
1099
1100     PlayerClass pc(player_ptr);
1101     const auto sniper_data = pc.get_specific_data<sniper_data_type>();
1102     const auto sniper_concent = sniper_data ? sniper_data->concent : 0;
1103
1104     /* Snipers can shot more critically with crossbows */
1105     power += ((power * sniper_concent) / 5);
1106     if (pc.equals(PlayerClassType::SNIPER) && (player_ptr->tval_ammo == ItemKindType::BOLT)) {
1107         power *= 2;
1108     }
1109
1110     /* Good bow makes more critical */
1111     power += plus_bow * 8 * (sniper_concent + 5);
1112     if (randint1(10000) > power) {
1113         return dam;
1114     }
1115
1116     const auto k = weight * randint1(500);
1117     if (k < 900) {
1118         msg_print(_("手ごたえがあった!", "It was a good hit!"));
1119         dam += (dam / 2);
1120     } else if (k < 1350) {
1121         msg_print(_("かなりの手ごたえがあった!", "It was a great hit!"));
1122         dam *= 2;
1123     } else {
1124         msg_print(_("会心の一撃だ!", "It was a superb hit!"));
1125         dam *= 3;
1126     }
1127
1128     return dam;
1129 }
1130
1131 /*!
1132  * @brief 射撃時クリティカルによるダメージ期待値修正計算(スナイパーの集中処理と武器経験値) / critical happens at i / 10000
1133  * @param player_ptr プレイヤーへの参照ポインタ
1134  * @param plus_ammo 矢弾のダメージ修正
1135  * @param plus_bow 弓のダメージ修正
1136  * @return ダメージ期待値
1137  * @note 基本ダメージ量と重量はこの部位では計算に加わらない。
1138  */
1139 int calc_crit_ratio_shot(PlayerType *player_ptr, int plus_ammo, int plus_bow)
1140 {
1141     auto *j_ptr = &player_ptr->inventory_list[INVEN_BOW];
1142
1143     /* Extract "shot" power */
1144     auto i = player_ptr->to_h_b + plus_ammo;
1145     const auto tval = j_ptr->bi_key.tval();
1146     const auto sval = j_ptr->bi_key.sval().value();
1147     if (player_ptr->tval_ammo == ItemKindType::BOLT) {
1148         i = (player_ptr->skill_thb + (player_ptr->weapon_exp[tval][sval] / 400 + i) * BTH_PLUS_ADJ);
1149     } else {
1150         i = (player_ptr->skill_thb + ((player_ptr->weapon_exp[tval][sval] - (PlayerSkill::weapon_exp_at(PlayerSkillRank::MASTER) / 2)) / 200 + i) * BTH_PLUS_ADJ);
1151     }
1152
1153     PlayerClass pc(player_ptr);
1154     auto sniper_data = pc.get_specific_data<sniper_data_type>();
1155     auto sniper_concent = sniper_data ? sniper_data->concent : 0;
1156
1157     /* Snipers can shot more critically with crossbows */
1158     i += ((i * sniper_concent) / 5);
1159     if (pc.equals(PlayerClassType::SNIPER) && (player_ptr->tval_ammo == ItemKindType::BOLT)) {
1160         i *= 2;
1161     }
1162
1163     /* Good bow makes more critical */
1164     i += plus_bow * 8 * (sniper_concent + 5);
1165
1166     if (i < 0) {
1167         i = 0;
1168     }
1169
1170     return i;
1171 }
1172
1173 /*!
1174  * @brief 射撃時クリティカルによるダメージ期待値修正計算(重量依存部分) / critical happens at i / 10000
1175  * @param weight 武器の重量
1176  * @param plus_ammo 矢弾のダメージ修正
1177  * @param plus_bow 弓のダメージ修正
1178  * @param dam 基本ダメージ量
1179  * @return ダメージ期待値
1180  */
1181 int calc_expect_crit_shot(PlayerType *player_ptr, WEIGHT weight, int plus_ammo, int plus_bow, int dam)
1182 {
1183     uint32_t num;
1184     int i, k, crit;
1185     i = calc_crit_ratio_shot(player_ptr, plus_ammo, plus_bow);
1186
1187     k = 0;
1188     num = 0;
1189
1190     crit = std::min(500, 900 / weight);
1191     num += dam * 3 / 2 * crit;
1192     k = crit;
1193
1194     crit = std::min(500, 1350 / weight);
1195     crit -= k;
1196     num += dam * 2 * crit;
1197     k += crit;
1198
1199     if (k < 500) {
1200         crit = 500 - k;
1201         num += dam * 3 * crit;
1202     }
1203
1204     num /= 500;
1205
1206     num *= i;
1207     num += (10000 - i) * dam;
1208     num /= 10000;
1209
1210     return num;
1211 }
1212
1213 /*!
1214  * @brief 攻撃時クリティカルによるダメージ期待値修正計算(重量と毒針処理) / critical happens at i / 10000
1215  * @param player_ptr プレイヤーへの参照ポインタ
1216  * @param weight 武器の重量
1217  * @param plus 武器のダメージ修正
1218  * @param dam 基本ダメージ
1219  * @param meichuu 命中値
1220  * @param dokubari 毒針処理か否か
1221  * @param impact 強撃かどうか
1222  * @return ダメージ期待値
1223  */
1224 int calc_expect_crit(PlayerType *player_ptr, WEIGHT weight, int plus, int dam, int16_t meichuu, bool dokubari, bool impact)
1225 {
1226     if (dokubari) {
1227         return dam;
1228     }
1229
1230     int i = (weight + (meichuu * 3 + plus * 5) + player_ptr->skill_thn);
1231     if (i < 0) {
1232         i = 0;
1233     }
1234
1235     // 通常ダメージdam、武器重量weightでクリティカルが発生した時のクリティカルダメージ期待値
1236     auto calc_weight_expect_dam = [](int dam, WEIGHT weight) {
1237         int sum = 0;
1238         for (int d = 1; d <= 650; ++d) {
1239             int k = weight + d;
1240             sum += std::get<0>(apply_critical_norm_damage(k, dam));
1241         }
1242         return sum / 650;
1243     };
1244
1245     uint32_t num = 0;
1246
1247     if (impact) {
1248         for (int d = 1; d <= 650; ++d) {
1249             num += calc_weight_expect_dam(dam, weight + d);
1250         }
1251         num /= 650;
1252     } else {
1253         num += calc_weight_expect_dam(dam, weight);
1254     }
1255
1256     int pow = PlayerClass(player_ptr).equals(PlayerClassType::NINJA) ? 4444 : 5000;
1257     if (impact) {
1258         pow /= 2;
1259     }
1260
1261     num *= i;
1262     num += (pow - i) * dam;
1263     num /= pow;
1264
1265     return num;
1266 }