OSDN Git Service

[Refactor] #3230 PlayerType::update に関わる処理を、RedrawingFlagsUpdaterに集約した
[hengbandforosx/hengbandosx.git] / src / object-use / throw-execution.cpp
1 /*!
2  * @file throw-util.cpp
3  * @brief 投擲処理関連クラス
4  * @date 2021/08/20
5  * @author Hourier
6  */
7
8 #include "object-use/throw-execution.h"
9 #include "action/weapon-shield.h"
10 #include "artifact/fixed-art-types.h"
11 #include "combat/attack-power-table.h"
12 #include "combat/shoot.h"
13 #include "combat/slaying.h"
14 #include "core/player-update-types.h"
15 #include "core/stuff-handler.h"
16 #include "core/window-redrawer.h"
17 #include "effect/attribute-types.h"
18 #include "effect/spells-effect-util.h"
19 #include "flavor/flavor-describer.h"
20 #include "flavor/object-flavor-types.h"
21 #include "floor/cave.h"
22 #include "floor/floor-object.h"
23 #include "floor/geometry.h"
24 #include "game-option/cheat-types.h"
25 #include "grid/feature-flag-types.h"
26 #include "grid/grid.h"
27 #include "inventory/inventory-object.h"
28 #include "inventory/inventory-slot-types.h"
29 #include "io/cursor.h"
30 #include "io/screen-util.h"
31 #include "main/sound-definitions-table.h"
32 #include "main/sound-of-music.h"
33 #include "monster-floor/monster-death.h"
34 #include "monster-floor/monster-summon.h"
35 #include "monster-floor/place-monster-types.h"
36 #include "monster/monster-damage.h"
37 #include "monster/monster-describer.h"
38 #include "monster/monster-info.h"
39 #include "monster/monster-pain-describer.h"
40 #include "monster/monster-status-setter.h"
41 #include "monster/monster-status.h"
42 #include "object-enchant/tr-types.h"
43 #include "object-hook/hook-expendable.h"
44 #include "object-hook/hook-weapon.h"
45 #include "object/item-tester-hooker.h"
46 #include "object/item-use-flags.h"
47 #include "object/object-broken.h"
48 #include "object/object-flags.h"
49 #include "object/object-info.h"
50 #include "object/object-stack.h"
51 #include "player-base/player-class.h"
52 #include "player-info/equipment-info.h"
53 #include "player-status/player-energy.h"
54 #include "player/player-status-table.h"
55 #include "racial/racial-android.h"
56 #include "specific-object/torch.h"
57 #include "system/baseitem-info.h"
58 #include "system/floor-type-definition.h"
59 #include "system/grid-type-definition.h"
60 #include "system/item-entity.h"
61 #include "system/monster-entity.h"
62 #include "system/player-type-definition.h"
63 #include "system/redrawing-flags-updater.h"
64 #include "target/target-checker.h"
65 #include "target/target-getter.h"
66 #include "term/screen-processor.h"
67 #include "timed-effect/player-blindness.h"
68 #include "timed-effect/player-hallucination.h"
69 #include "timed-effect/timed-effects.h"
70 #include "util/bit-flags-calculator.h"
71 #include "util/string-processor.h"
72 #include "view/display-messages.h"
73 #include "view/object-describer.h"
74 #include "wizard/wizard-messages.h"
75
76 ObjectThrowEntity::ObjectThrowEntity(PlayerType *player_ptr, ItemEntity *q_ptr, const int delay_factor_val, const int mult, const bool boomerang, const OBJECT_IDX shuriken)
77     : q_ptr(q_ptr)
78     , player_ptr(player_ptr)
79     , shuriken(shuriken)
80     , mult(mult)
81     , msec(delay_factor_val)
82     , boomerang(boomerang)
83 {
84 }
85
86 bool ObjectThrowEntity::check_can_throw()
87 {
88     if (!this->check_what_throw()) {
89         return false;
90     }
91
92     if (this->o_ptr->is_cursed() && (this->item >= INVEN_MAIN_HAND)) {
93         msg_print(_("ふーむ、どうやら呪われているようだ。", "Hmmm, it seems to be cursed."));
94         return false;
95     }
96
97     const auto is_spike = this->o_ptr->bi_key.tval() == ItemKindType::SPIKE;
98     if (this->player_ptr->current_floor_ptr->inside_arena && !this->boomerang && !is_spike) {
99         msg_print(_("アリーナではアイテムを使えない!", "You're in the arena now. This is hand-to-hand!"));
100         msg_print(nullptr);
101         return false;
102     }
103
104     return true;
105 }
106
107 void ObjectThrowEntity::calc_throw_range()
108 {
109     this->q_ptr->copy_from(this->o_ptr);
110     this->obj_flags = object_flags(this->q_ptr);
111     torch_flags(this->q_ptr, this->obj_flags);
112     distribute_charges(this->o_ptr, this->q_ptr, 1);
113     this->q_ptr->number = 1;
114     this->o_name = describe_flavor(this->player_ptr, this->q_ptr, OD_OMIT_PREFIX);
115     if (this->player_ptr->mighty_throw) {
116         this->mult += 3;
117     }
118
119     auto mul = 10 + 2 * (this->mult - 1);
120     auto div = ((this->q_ptr->weight > 10) ? this->q_ptr->weight : 10);
121     if ((this->obj_flags.has(TR_THROW)) || this->boomerang) {
122         div /= 2;
123     }
124
125     this->tdis = (adj_str_blow[this->player_ptr->stat_index[A_STR]] + 20) * mul / div;
126     if (this->tdis > mul) {
127         this->tdis = mul;
128     }
129 }
130
131 bool ObjectThrowEntity::calc_throw_grid()
132 {
133     if (this->shuriken >= 0) {
134         this->ty = randint0(101) - 50 + this->player_ptr->y;
135         this->tx = randint0(101) - 50 + this->player_ptr->x;
136         return true;
137     }
138
139     project_length = this->tdis + 1;
140     DIRECTION dir;
141     if (!get_aim_dir(this->player_ptr, &dir)) {
142         return false;
143     }
144
145     this->tx = this->player_ptr->x + 99 * ddx[dir];
146     this->ty = this->player_ptr->y + 99 * ddy[dir];
147     if ((dir == 5) && target_okay(this->player_ptr)) {
148         this->tx = target_col;
149         this->ty = target_row;
150     }
151
152     project_length = 0;
153     return true;
154 }
155
156 void ObjectThrowEntity::reflect_inventory_by_throw()
157 {
158     if (this->q_ptr->is_specific_artifact(FixedArtifactId::MJOLLNIR) || this->q_ptr->is_specific_artifact(FixedArtifactId::AEGISFANG) || this->boomerang) {
159         this->return_when_thrown = true;
160     }
161
162     if (this->item < 0) {
163         floor_item_increase(this->player_ptr, 0 - this->item, -1);
164         floor_item_optimize(this->player_ptr, 0 - this->item);
165         return;
166     }
167
168     inven_item_increase(this->player_ptr, this->item, -1);
169     if (!this->return_when_thrown) {
170         inven_item_describe(this->player_ptr, this->item);
171     }
172
173     inven_item_optimize(this->player_ptr, this->item);
174 }
175
176 void ObjectThrowEntity::set_class_specific_throw_params()
177 {
178     PlayerEnergy energy(this->player_ptr);
179     energy.set_player_turn_energy(100);
180     PlayerClass pc(this->player_ptr);
181     if (pc.equals(PlayerClassType::ROGUE) || pc.equals(PlayerClassType::NINJA)) {
182         energy.sub_player_turn_energy(this->player_ptr->lev);
183     }
184
185     this->y = this->player_ptr->y;
186     this->x = this->player_ptr->x;
187     handle_stuff(this->player_ptr);
188     const auto tval = this->q_ptr->bi_key.tval();
189     const auto is_spike = tval == ItemKindType::SPIKE;
190     const auto is_sword = tval == ItemKindType::SWORD;
191     this->shuriken = pc.equals(PlayerClassType::NINJA) && (is_spike || ((this->obj_flags.has(TR_THROW)) && is_sword));
192 }
193
194 void ObjectThrowEntity::set_racial_chance()
195 {
196     auto compensation = this->obj_flags.has(TR_THROW) ? this->q_ptr->to_h : 0;
197     this->chance = this->player_ptr->skill_tht + (this->player_ptr->to_h_b + compensation) * BTH_PLUS_ADJ;
198     if (this->shuriken != 0) {
199         this->chance *= 2;
200     }
201 }
202
203 void ObjectThrowEntity::exe_throw()
204 {
205     this->cur_dis = 0;
206     while (this->cur_dis <= this->tdis) {
207         if ((this->y == this->ty) && (this->x == this->tx)) {
208             break;
209         }
210
211         if (this->check_racial_target_bold()) {
212             break;
213         }
214
215         this->check_racial_target_seen();
216         if (this->check_racial_target_monster()) {
217             continue;
218         }
219
220         auto *floor_ptr = this->player_ptr->current_floor_ptr;
221         this->g_ptr = &floor_ptr->grid_array[this->y][this->x];
222         this->m_ptr = &floor_ptr->m_list[this->g_ptr->m_idx];
223         this->m_name = monster_name(this->player_ptr, this->g_ptr->m_idx);
224         this->visible = this->m_ptr->ml;
225         this->hit_body = true;
226         this->attack_racial_power();
227         break;
228     }
229 }
230
231 void ObjectThrowEntity::display_figurine_throw()
232 {
233     if ((this->q_ptr->bi_key.tval() != ItemKindType::FIGURINE) || this->player_ptr->current_floor_ptr->inside_arena) {
234         return;
235     }
236
237     this->corruption_possibility = 100;
238     auto figure_r_idx = i2enum<MonsterRaceId>(this->q_ptr->pval);
239     if (!(summon_named_creature(this->player_ptr, 0, this->y, this->x, figure_r_idx, !(this->q_ptr->is_cursed()) ? PM_FORCE_PET : PM_NONE))) {
240         msg_print(_("人形は捻じ曲がり砕け散ってしまった!", "The Figurine writhes and then shatters."));
241         return;
242     }
243
244     if (this->q_ptr->is_cursed()) {
245         msg_print(_("これはあまり良くない気がする。", "You have a bad feeling about this."));
246     }
247 }
248
249 void ObjectThrowEntity::display_potion_throw()
250 {
251     if (!this->q_ptr->is_potion()) {
252         return;
253     }
254
255     if (!this->hit_body && !this->hit_wall && (randint1(100) >= this->corruption_possibility)) {
256         this->corruption_possibility = 0;
257         return;
258     }
259
260     msg_format(_("%sは砕け散った!", "The %s shatters!"), this->o_name.data());
261     if (!potion_smash_effect(this->player_ptr, 0, this->y, this->x, this->q_ptr->bi_id)) {
262         this->do_drop = false;
263         return;
264     }
265
266     auto *floor_ptr = this->player_ptr->current_floor_ptr;
267     auto *angry_m_ptr = &floor_ptr->m_list[floor_ptr->grid_array[this->y][this->x].m_idx];
268     if ((floor_ptr->grid_array[this->y][this->x].m_idx == 0) || !angry_m_ptr->is_friendly() || angry_m_ptr->is_invulnerable()) {
269         this->do_drop = false;
270         return;
271     }
272
273     const auto angry_m_name = monster_desc(this->player_ptr, angry_m_ptr, 0);
274     msg_format(_("%sは怒った!", "%s^ gets angry!"), angry_m_name.data());
275     set_hostile(this->player_ptr, &floor_ptr->m_list[floor_ptr->grid_array[this->y][this->x].m_idx]);
276     this->do_drop = false;
277 }
278
279 void ObjectThrowEntity::check_boomerang_throw()
280 {
281     if (!this->return_when_thrown) {
282         return;
283     }
284
285     this->back_chance = randint1(30) + 20 + ((int)(adj_dex_th[this->player_ptr->stat_index[A_DEX]]) - 128);
286     this->super_boomerang = ((this->q_ptr->is_specific_artifact(FixedArtifactId::MJOLLNIR) || this->q_ptr->is_specific_artifact(FixedArtifactId::AEGISFANG)) && this->boomerang);
287     this->corruption_possibility = -1;
288     if (this->boomerang) {
289         this->back_chance += 4 + randint1(5);
290     }
291
292     if (this->super_boomerang) {
293         this->back_chance += 100;
294     }
295
296     this->o2_name = describe_flavor(this->player_ptr, this->q_ptr, OD_OMIT_PREFIX | OD_NAME_ONLY);
297     this->process_boomerang_throw();
298 }
299
300 void ObjectThrowEntity::process_boomerang_back()
301 {
302     if (this->come_back) {
303         if ((this->item != INVEN_MAIN_HAND) && (this->item != INVEN_SUB_HAND)) {
304             store_item_to_inventory(this->player_ptr, this->q_ptr);
305             this->do_drop = false;
306             return;
307         }
308
309         this->o_ptr = &player_ptr->inventory_list[this->item];
310         this->o_ptr->copy_from(this->q_ptr);
311         this->player_ptr->equip_cnt++;
312         auto &rfu = RedrawingFlagsUpdater::get_instance();
313         const auto flags = {
314             StatusRedrawingFlag::BONUS,
315             StatusRedrawingFlag::TORCH,
316             StatusRedrawingFlag::MP,
317         };
318         rfu.set_flags(flags);
319         this->player_ptr->window_flags |= PW_EQUIPMENT;
320         this->do_drop = false;
321         return;
322     }
323
324     if (this->equiped_item) {
325         verify_equip_slot(this->player_ptr, this->item);
326         calc_android_exp(this->player_ptr);
327     }
328 }
329
330 void ObjectThrowEntity::drop_thrown_item()
331 {
332     if (!this->do_drop) {
333         return;
334     }
335
336     auto is_bold = cave_has_flag_bold(this->player_ptr->current_floor_ptr, this->y, this->x, TerrainCharacteristics::PROJECT);
337     auto drop_y = is_bold ? this->y : this->prev_y;
338     auto drop_x = is_bold ? this->x : this->prev_x;
339     (void)drop_near(this->player_ptr, this->q_ptr, this->corruption_possibility, drop_y, drop_x);
340 }
341
342 bool ObjectThrowEntity::check_what_throw()
343 {
344     if (this->shuriken >= 0) {
345         this->item = this->shuriken;
346         this->o_ptr = &this->player_ptr->inventory_list[this->item];
347         return true;
348     }
349
350     if (this->boomerang) {
351         return this->check_throw_boomerang();
352     }
353
354     concptr q, s;
355     q = _("どのアイテムを投げますか? ", "Throw which item? ");
356     s = _("投げるアイテムがない。", "You have nothing to throw.");
357     this->o_ptr = choose_object(this->player_ptr, &this->item, q, s, USE_INVEN | USE_FLOOR | USE_EQUIP);
358     if (!this->o_ptr) {
359         flush();
360         return false;
361     }
362
363     return true;
364 }
365
366 bool ObjectThrowEntity::check_throw_boomerang()
367 {
368     if (has_melee_weapon(this->player_ptr, INVEN_MAIN_HAND) && has_melee_weapon(this->player_ptr, INVEN_SUB_HAND)) {
369         concptr q, s;
370         q = _("どの武器を投げますか? ", "Throw which item? ");
371         s = _("投げる武器がない。", "You have nothing to throw.");
372         this->o_ptr = choose_object(this->player_ptr, &this->item, q, s, USE_EQUIP, FuncItemTester(&ItemEntity::is_throwable));
373         if (!this->o_ptr) {
374             flush();
375             return false;
376         }
377
378         return true;
379     }
380
381     if (has_melee_weapon(this->player_ptr, INVEN_SUB_HAND)) {
382         this->item = INVEN_SUB_HAND;
383         this->o_ptr = &this->player_ptr->inventory_list[this->item];
384         return true;
385     }
386
387     this->item = INVEN_MAIN_HAND;
388     this->o_ptr = &this->player_ptr->inventory_list[this->item];
389     return true;
390 }
391
392 bool ObjectThrowEntity::check_racial_target_bold()
393 {
394     this->ny[this->cur_dis] = this->y;
395     this->nx[this->cur_dis] = this->x;
396     mmove2(&this->ny[this->cur_dis], &this->nx[this->cur_dis], this->player_ptr->y, this->player_ptr->x, this->ty, this->tx);
397     auto *floor_ptr = this->player_ptr->current_floor_ptr;
398     if (cave_has_flag_bold(floor_ptr, this->ny[this->cur_dis], this->nx[this->cur_dis], TerrainCharacteristics::PROJECT)) {
399         return false;
400     }
401
402     this->hit_wall = true;
403     const auto is_figurine = this->q_ptr->bi_key.tval() == ItemKindType::FIGURINE;
404     return is_figurine || this->q_ptr->is_potion() || (floor_ptr->grid_array[this->ny[this->cur_dis]][this->nx[this->cur_dis]].m_idx == 0);
405 }
406
407 void ObjectThrowEntity::check_racial_target_seen()
408 {
409     if (!panel_contains(this->ny[this->cur_dis], this->nx[this->cur_dis]) || !player_can_see_bold(this->player_ptr, this->ny[this->cur_dis], this->nx[this->cur_dis])) {
410         term_xtra(TERM_XTRA_DELAY, this->msec);
411         return;
412     }
413
414     if (this->msec <= 0) {
415         return;
416     }
417
418     const auto c = this->q_ptr->get_symbol();
419     const auto a = this->q_ptr->get_color();
420     print_rel(this->player_ptr, c, a, this->ny[this->cur_dis], this->nx[this->cur_dis]);
421     move_cursor_relative(this->ny[this->cur_dis], this->nx[this->cur_dis]);
422     term_fresh();
423     term_xtra(TERM_XTRA_DELAY, this->msec);
424     lite_spot(this->player_ptr, this->ny[this->cur_dis], this->nx[this->cur_dis]);
425     term_fresh();
426 }
427
428 bool ObjectThrowEntity::check_racial_target_monster()
429 {
430     this->prev_y = this->y;
431     this->prev_x = this->x;
432     this->x = this->nx[this->cur_dis];
433     this->y = this->ny[this->cur_dis];
434     this->cur_dis++;
435     return this->player_ptr->current_floor_ptr->grid_array[this->y][this->x].m_idx == 0;
436 }
437
438 void ObjectThrowEntity::attack_racial_power()
439 {
440     if (!test_hit_fire(this->player_ptr, this->chance - this->cur_dis, this->m_ptr, this->m_ptr->ml, this->o_name)) {
441         return;
442     }
443
444     this->display_attack_racial_power();
445     this->calc_racial_power_damage();
446     msg_format_wizard(this->player_ptr, CHEAT_MONSTER, _("%dのダメージを与えた。(残りHP %d/%d(%d))", "You do %d damage. (left HP %d/%d(%d))"), this->tdam,
447         this->m_ptr->hp - this->tdam, this->m_ptr->maxhp, this->m_ptr->max_maxhp);
448
449     auto fear = false;
450     AttributeFlags attribute_flags{};
451     attribute_flags.set(AttributeType::PLAYER_SHOOT);
452     if (is_active_torch(this->o_ptr)) {
453         attribute_flags.set(AttributeType::FIRE);
454     }
455
456     MonsterDamageProcessor mdp(this->player_ptr, this->g_ptr->m_idx, this->tdam, &fear, attribute_flags);
457     if (mdp.mon_take_hit(extract_note_dies(this->m_ptr->get_real_r_idx()))) {
458         return;
459     }
460
461     if (const auto pain_message = MonsterPainDescriber(player_ptr, this->g_ptr->m_idx).describe(this->tdam);
462         !pain_message.empty()) {
463         msg_print(pain_message);
464     }
465
466     if ((this->tdam > 0) && !this->q_ptr->is_potion()) {
467         anger_monster(this->player_ptr, this->m_ptr);
468     }
469
470     if (fear && this->m_ptr->ml) {
471         sound(SOUND_FLEE);
472         msg_format(_("%s^は恐怖して逃げ出した!", "%s^ flees in terror!"), this->m_name.data());
473     }
474 }
475
476 void ObjectThrowEntity::display_attack_racial_power()
477 {
478     if (!this->visible) {
479         msg_format(_("%sが敵を捕捉した。", "The %s finds a mark."), this->o_name.data());
480         return;
481     }
482
483     msg_format(_("%sが%sに命中した。", "The %s hits %s."), this->o_name.data(), this->m_name.data());
484     if (!this->m_ptr->ml) {
485         return;
486     }
487
488     if (!this->player_ptr->effects()->hallucination()->is_hallucinated()) {
489         monster_race_track(this->player_ptr, this->m_ptr->ap_r_idx);
490     }
491
492     health_track(this->player_ptr, this->g_ptr->m_idx);
493 }
494
495 void ObjectThrowEntity::calc_racial_power_damage()
496 {
497     auto dd = this->q_ptr->dd;
498     auto ds = this->q_ptr->ds;
499     torch_dice(this->q_ptr, &dd, &ds);
500     this->tdam = damroll(dd, ds);
501     this->tdam = calc_attack_damage_with_slay(this->player_ptr, this->q_ptr, this->tdam, this->m_ptr, HISSATSU_NONE, true);
502     this->tdam = critical_shot(this->player_ptr, this->q_ptr->weight, this->q_ptr->to_h, 0, this->tdam);
503     this->tdam += (this->q_ptr->to_d > 0 ? 1 : -1) * this->q_ptr->to_d;
504     if (this->boomerang) {
505         this->tdam *= (this->mult + this->player_ptr->num_blow[this->item - INVEN_MAIN_HAND]);
506         this->tdam += this->player_ptr->to_d_m;
507     } else if (this->obj_flags.has(TR_THROW)) {
508         this->tdam *= (3 + this->mult);
509         this->tdam += this->player_ptr->to_d_m;
510     } else {
511         this->tdam *= this->mult;
512     }
513
514     if (this->shuriken != 0) {
515         this->tdam += ((this->player_ptr->lev + 30) * (this->player_ptr->lev + 30) - 900) / 55;
516     }
517
518     if (this->tdam < 0) {
519         this->tdam = 0;
520     }
521
522     this->tdam = mon_damage_mod(this->player_ptr, this->m_ptr, this->tdam, false);
523 }
524
525 void ObjectThrowEntity::process_boomerang_throw()
526 {
527     if ((this->back_chance <= 30) || (one_in_(100) && !this->super_boomerang)) {
528         msg_format(_("%sが返ってこなかった!", "%s doesn't come back!"), this->o2_name.data());
529         return;
530     }
531
532     for (auto i = this->cur_dis - 1; i > 0; i--) {
533         if (!panel_contains(this->ny[i], this->nx[i]) || !player_can_see_bold(this->player_ptr, this->ny[i], this->nx[i])) {
534             term_xtra(TERM_XTRA_DELAY, this->msec);
535             continue;
536         }
537
538         const auto c = this->q_ptr->get_symbol();
539         const auto a = this->q_ptr->get_color();
540         if (this->msec <= 0) {
541             continue;
542         }
543
544         print_rel(this->player_ptr, c, a, this->ny[i], this->nx[i]);
545         move_cursor_relative(this->ny[i], this->nx[i]);
546         term_fresh();
547         term_xtra(TERM_XTRA_DELAY, this->msec);
548         lite_spot(this->player_ptr, this->ny[i], this->nx[i]);
549         term_fresh();
550     }
551
552     this->display_boomerang_throw();
553 }
554
555 void ObjectThrowEntity::display_boomerang_throw()
556 {
557     const auto is_blind = this->player_ptr->effects()->blindness()->is_blind();
558     if ((this->back_chance > 37) && !is_blind && (this->item >= 0)) {
559         msg_format(_("%sが手元に返ってきた。", "%s comes back to you."), this->o2_name.data());
560         this->come_back = true;
561         return;
562     }
563
564     auto back_message = this->item >= 0 ? _("%sを受け損ねた!", "%s comes back, but you can't catch!") : _("%sが返ってきた。", "%s comes back.");
565     msg_format(back_message, this->o2_name.data());
566     this->y = this->player_ptr->y;
567     this->x = this->player_ptr->x;
568 }