OSDN Git Service

Merge pull request #3367 from Hourier/Remove-memcpy-1
[hengbandforosx/hengbandosx.git] / src / inventory / inventory-curse.cpp
1 #include "inventory/inventory-curse.h"
2 #include "artifact/fixed-art-types.h"
3 #include "core/asking-player.h"
4 #include "core/disturbance.h"
5 #include "flavor/flavor-describer.h"
6 #include "flavor/object-flavor-types.h"
7 #include "inventory/inventory-slot-types.h"
8 #include "io/files-util.h"
9 #include "monster-floor/monster-summon.h"
10 #include "monster-floor/place-monster-types.h"
11 #include "object-enchant/item-feeling.h"
12 #include "object-enchant/object-curse.h"
13 #include "object-enchant/special-object-flags.h"
14 #include "object-enchant/tr-types.h"
15 #include "object-enchant/trc-types.h"
16 #include "object/object-flags.h"
17 #include "perception/object-perception.h"
18 #include "player-base/player-race.h"
19 #include "player-info/race-types.h"
20 #include "player/player-damage.h"
21 #include "player/player-status-flags.h"
22 #include "player/player-status.h"
23 #include "spell-kind/spells-random.h"
24 #include "spell-kind/spells-teleport.h"
25 #include "spell/summon-types.h"
26 #include "status/bad-status-setter.h"
27 #include "status/buff-setter.h"
28 #include "system/floor-type-definition.h"
29 #include "system/item-entity.h"
30 #include "system/player-type-definition.h"
31 #include "system/redrawing-flags-updater.h"
32 #include "util/bit-flags-calculator.h"
33 #include "util/quarks.h"
34 #include "util/string-processor.h"
35 #include "view/display-messages.h"
36 #include <optional>
37 #include <string>
38
39 // clang-format off
40 namespace {
41 const EnumClassFlagGroup<CurseTraitType> TRC_P_FLAG_MASK({
42     CurseTraitType::TY_CURSE,
43     CurseTraitType::DRAIN_EXP,
44     CurseTraitType::ADD_L_CURSE,
45     CurseTraitType::ADD_H_CURSE,
46     CurseTraitType::CALL_ANIMAL,
47     CurseTraitType::CALL_DEMON,
48     CurseTraitType::CALL_DRAGON,
49     CurseTraitType::COWARDICE,
50     CurseTraitType::TELEPORT,
51     CurseTraitType::DRAIN_HP,
52     CurseTraitType::DRAIN_MANA,
53     CurseTraitType::CALL_UNDEAD,
54     CurseTraitType::BERS_RAGE,
55     CurseTraitType::PERSISTENT_CURSE });
56 const EnumClassFlagGroup<CurseSpecialTraitType> TRCS_P_FLAG_MASK({
57     CurseSpecialTraitType::TELEPORT_SELF,
58     CurseSpecialTraitType::CHAINSWORD });
59 }
60 // clang-format on
61
62 static bool is_specific_curse(CurseTraitType flag)
63 {
64     switch (flag) {
65     case CurseTraitType::ADD_L_CURSE:
66     case CurseTraitType::ADD_H_CURSE:
67     case CurseTraitType::DRAIN_HP:
68     case CurseTraitType::DRAIN_MANA:
69     case CurseTraitType::CALL_ANIMAL:
70     case CurseTraitType::CALL_DEMON:
71     case CurseTraitType::CALL_DRAGON:
72     case CurseTraitType::CALL_UNDEAD:
73     case CurseTraitType::COWARDICE:
74     case CurseTraitType::LOW_MELEE:
75     case CurseTraitType::LOW_AC:
76     case CurseTraitType::HARD_SPELL:
77     case CurseTraitType::FAST_DIGEST:
78     case CurseTraitType::SLOW_REGEN:
79     case CurseTraitType::BERS_RAGE:
80     case CurseTraitType::PERSISTENT_CURSE:
81         return true;
82     default:
83         return false;
84     }
85 }
86
87 static void choise_cursed_item(CurseTraitType flag, ItemEntity *o_ptr, int *choices, int *number, int item_num)
88 {
89     if (!is_specific_curse(flag)) {
90         return;
91     }
92
93     tr_type cf = TR_STR;
94     auto flags = object_flags(o_ptr);
95     switch (flag) {
96     case CurseTraitType::ADD_L_CURSE:
97         cf = TR_ADD_L_CURSE;
98         break;
99     case CurseTraitType::ADD_H_CURSE:
100         cf = TR_ADD_H_CURSE;
101         break;
102     case CurseTraitType::DRAIN_HP:
103         cf = TR_DRAIN_HP;
104         break;
105     case CurseTraitType::DRAIN_MANA:
106         cf = TR_DRAIN_MANA;
107         break;
108     case CurseTraitType::CALL_ANIMAL:
109         cf = TR_CALL_ANIMAL;
110         break;
111     case CurseTraitType::CALL_DEMON:
112         cf = TR_CALL_DEMON;
113         break;
114     case CurseTraitType::CALL_DRAGON:
115         cf = TR_CALL_DRAGON;
116         break;
117     case CurseTraitType::CALL_UNDEAD:
118         cf = TR_CALL_UNDEAD;
119         break;
120     case CurseTraitType::COWARDICE:
121         cf = TR_COWARDICE;
122         break;
123     case CurseTraitType::LOW_MELEE:
124         cf = TR_LOW_MELEE;
125         break;
126     case CurseTraitType::LOW_AC:
127         cf = TR_LOW_AC;
128         break;
129     case CurseTraitType::HARD_SPELL:
130         cf = TR_HARD_SPELL;
131         break;
132     case CurseTraitType::FAST_DIGEST:
133         cf = TR_FAST_DIGEST;
134         break;
135     case CurseTraitType::SLOW_REGEN:
136         cf = TR_SLOW_REGEN;
137         break;
138     case CurseTraitType::BERS_RAGE:
139         cf = TR_BERS_RAGE;
140         break;
141     case CurseTraitType::PERSISTENT_CURSE:
142         cf = TR_PERSISTENT_CURSE;
143         break;
144     case CurseTraitType::VUL_CURSE:
145         cf = TR_VUL_CURSE;
146         break;
147     default:
148         break;
149     }
150
151     if (flags.has_not(cf)) {
152         return;
153     }
154
155     choices[*number] = item_num;
156     (*number)++;
157 }
158
159 /*!
160  * @brief 現在呪いを保持している装備品を一つランダムに探し出す / Choose one of items that have cursed flag
161  * @param flag 探し出したい呪いフラグ配列
162  * @return 該当の呪いが一つでもあった場合にランダムに選ばれた装備品のオブジェクト構造体参照ポインタを返す。\n
163  * 呪いがない場合nullptrを返す。
164  */
165 ItemEntity *choose_cursed_obj_name(PlayerType *player_ptr, CurseTraitType flag)
166 {
167     int choices[INVEN_TOTAL - INVEN_MAIN_HAND];
168     int number = 0;
169     if (player_ptr->cursed.has_not(flag)) {
170         return nullptr;
171     }
172
173     for (int i = INVEN_MAIN_HAND; i < INVEN_TOTAL; i++) {
174         auto *o_ptr = &player_ptr->inventory_list[i];
175         if (o_ptr->curse_flags.has(flag)) {
176             choices[number] = i;
177             number++;
178             continue;
179         }
180
181         choise_cursed_item(flag, o_ptr, choices, &number, i);
182     }
183
184     return &player_ptr->inventory_list[choices[randint0(number)]];
185 }
186
187 /*!
188  * @brief 呪われている、トランプエゴ等による装備品由来のテレポートを実行する
189  * @param player_ptr プレイヤーへの参照ポインタ
190  */
191 static void curse_teleport(PlayerType *player_ptr)
192 {
193     if ((player_ptr->cursed_special.has_not(CurseSpecialTraitType::TELEPORT_SELF)) || !one_in_(200)) {
194         return;
195     }
196
197     ItemEntity *o_ptr;
198     int i_keep = 0, count = 0;
199     for (int i = INVEN_MAIN_HAND; i < INVEN_TOTAL; i++) {
200         o_ptr = &player_ptr->inventory_list[i];
201         if (!o_ptr->is_valid()) {
202             continue;
203         }
204
205         auto flags = object_flags(o_ptr);
206
207         if (flags.has_not(TR_TELEPORT)) {
208             continue;
209         }
210
211         if (o_ptr->is_inscribed() && angband_strchr(o_ptr->inscription->data(), '.')) {
212             continue;
213         }
214
215         count++;
216         if (one_in_(count)) {
217             i_keep = i;
218         }
219     }
220
221     o_ptr = &player_ptr->inventory_list[i_keep];
222     const auto item_name = describe_flavor(player_ptr, o_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
223     msg_format(_("%sがテレポートの能力を発動させようとしている。", "Your %s tries to teleport you."), item_name.data());
224     if (get_check_strict(player_ptr, _("テレポートしますか?", "Teleport? "), CHECK_OKAY_CANCEL)) {
225         disturb(player_ptr, false, true);
226         teleport_player(player_ptr, 50, TELEPORT_SPONTANEOUS);
227     } else {
228         msg_format(_("%sに{.}(ピリオド)と銘を刻むと発動を抑制できます。", "You can inscribe {.} on your %s to disable random teleportation. "), item_name.data());
229         disturb(player_ptr, true, true);
230     }
231 }
232
233 /*!
234  * @details 元々呪い効果の発揮ルーチン中にいたので、整合性保持のためここに置いておく
235  */
236 static void occur_chainsword_effect(PlayerType *player_ptr)
237 {
238     constexpr auto chance_noise = 100;
239     if ((player_ptr->cursed_special.has_not(CurseSpecialTraitType::CHAINSWORD)) || !one_in_(chance_noise)) {
240         return;
241     }
242
243     const auto noise = get_random_line(_("chainswd_j.txt", "chainswd.txt"), 0);
244     if (noise.has_value()) {
245         msg_print(noise.value());
246     }
247
248     disturb(player_ptr, false, false);
249 }
250
251 static void curse_drain_exp(PlayerType *player_ptr)
252 {
253     if (PlayerRace(player_ptr).equals(PlayerRaceType::ANDROID) || (player_ptr->cursed.has_not(CurseTraitType::DRAIN_EXP)) || !one_in_(4)) {
254         return;
255     }
256
257     player_ptr->exp -= (player_ptr->lev + 1) / 2;
258     if (player_ptr->exp < 0) {
259         player_ptr->exp = 0;
260     }
261
262     player_ptr->max_exp -= (player_ptr->lev + 1) / 2;
263     if (player_ptr->max_exp < 0) {
264         player_ptr->max_exp = 0;
265     }
266
267     check_experience(player_ptr);
268 }
269
270 static void multiply_low_curse(PlayerType *player_ptr)
271 {
272     if ((player_ptr->cursed.has_not(CurseTraitType::ADD_L_CURSE)) || !one_in_(2000)) {
273         return;
274     }
275
276     auto *o_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::ADD_L_CURSE);
277     auto new_curse = get_curse(0, o_ptr);
278     if (o_ptr->curse_flags.has(new_curse)) {
279         return;
280     }
281
282     const auto item_name = describe_flavor(player_ptr, o_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
283     o_ptr->curse_flags.set(new_curse);
284     msg_format(_("悪意に満ちた黒いオーラが%sをとりまいた...", "There is a malignant black aura surrounding your %s..."), item_name.data());
285     o_ptr->feeling = FEEL_NONE;
286     RedrawingFlagsUpdater::get_instance().set_flag(StatusRedrawingFlag::BONUS);
287 }
288
289 static void multiply_high_curse(PlayerType *player_ptr)
290 {
291     if ((player_ptr->cursed.has_not(CurseTraitType::ADD_H_CURSE)) || !one_in_(2000)) {
292         return;
293     }
294
295     ItemEntity *o_ptr;
296     o_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::ADD_H_CURSE);
297     auto new_curse = get_curse(1, o_ptr);
298     if (o_ptr->curse_flags.has(new_curse)) {
299         return;
300     }
301
302     const auto item_name = describe_flavor(player_ptr, o_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
303     o_ptr->curse_flags.set(new_curse);
304     msg_format(_("悪意に満ちた黒いオーラが%sをとりまいた...", "There is a malignant black aura surrounding your %s..."), item_name.data());
305     o_ptr->feeling = FEEL_NONE;
306     RedrawingFlagsUpdater::get_instance().set_flag(StatusRedrawingFlag::BONUS);
307 }
308
309 static void persist_curse(PlayerType *player_ptr)
310 {
311     if ((player_ptr->cursed.has_not(CurseTraitType::PERSISTENT_CURSE)) || !one_in_(500)) {
312         return;
313     }
314
315     ItemEntity *o_ptr;
316     o_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::PERSISTENT_CURSE);
317     if (o_ptr->curse_flags.has(CurseTraitType::HEAVY_CURSE)) {
318         return;
319     }
320
321     const auto item_name = describe_flavor(player_ptr, o_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
322     o_ptr->curse_flags.set(CurseTraitType::HEAVY_CURSE);
323     msg_format(_("悪意に満ちた黒いオーラが%sをとりまいた...", "There is a malignant black aura surrounding your %s..."), item_name.data());
324     o_ptr->feeling = FEEL_NONE;
325     RedrawingFlagsUpdater::get_instance().set_flag(StatusRedrawingFlag::BONUS);
326 }
327
328 static void curse_call_monster(PlayerType *player_ptr)
329 {
330     const int call_type = PM_ALLOW_GROUP | PM_ALLOW_UNIQUE | PM_NO_PET;
331     const int obj_desc_type = OD_OMIT_PREFIX | OD_NAME_ONLY;
332     auto *floor_ptr = player_ptr->current_floor_ptr;
333     if (player_ptr->cursed.has(CurseTraitType::CALL_ANIMAL) && one_in_(2500)) {
334         if (summon_specific(player_ptr, 0, player_ptr->y, player_ptr->x, floor_ptr->dun_level, SUMMON_ANIMAL, call_type)) {
335             const auto item_name = describe_flavor(player_ptr, choose_cursed_obj_name(player_ptr, CurseTraitType::CALL_ANIMAL), obj_desc_type);
336             msg_format(_("%sが動物を引き寄せた!", "Your %s has attracted an animal!"), item_name.data());
337             disturb(player_ptr, false, true);
338         }
339     }
340
341     if (player_ptr->cursed.has(CurseTraitType::CALL_DEMON) && one_in_(1111)) {
342         if (summon_specific(player_ptr, 0, player_ptr->y, player_ptr->x, floor_ptr->dun_level, SUMMON_DEMON, call_type)) {
343             const auto item_name = describe_flavor(player_ptr, choose_cursed_obj_name(player_ptr, CurseTraitType::CALL_DEMON), obj_desc_type);
344             msg_format(_("%sが悪魔を引き寄せた!", "Your %s has attracted a demon!"), item_name.data());
345             disturb(player_ptr, false, true);
346         }
347     }
348
349     if (player_ptr->cursed.has(CurseTraitType::CALL_DRAGON) && one_in_(800)) {
350         if (summon_specific(player_ptr, 0, player_ptr->y, player_ptr->x, floor_ptr->dun_level, SUMMON_DRAGON, call_type)) {
351             const auto item_name = describe_flavor(player_ptr, choose_cursed_obj_name(player_ptr, CurseTraitType::CALL_DRAGON), obj_desc_type);
352             msg_format(_("%sがドラゴンを引き寄せた!", "Your %s has attracted a dragon!"), item_name.data());
353             disturb(player_ptr, false, true);
354         }
355     }
356
357     if (player_ptr->cursed.has(CurseTraitType::CALL_UNDEAD) && one_in_(1111)) {
358         if (summon_specific(player_ptr, 0, player_ptr->y, player_ptr->x, floor_ptr->dun_level, SUMMON_UNDEAD, call_type)) {
359             const auto item_name = describe_flavor(player_ptr, choose_cursed_obj_name(player_ptr, CurseTraitType::CALL_UNDEAD), obj_desc_type);
360             msg_format(_("%sが死霊を引き寄せた!", "Your %s has attracted an undead!"), item_name.data());
361             disturb(player_ptr, false, true);
362         }
363     }
364 }
365
366 static void curse_cowardice(PlayerType *player_ptr)
367 {
368     if (player_ptr->cursed.has_not(CurseTraitType::COWARDICE)) {
369         return;
370     }
371
372     auto *o_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::COWARDICE);
373     auto chance = 1500;
374     short duration = 13 + randint1(26);
375     if (o_ptr->curse_flags.has(CurseTraitType::HEAVY_CURSE)) {
376         chance = 150;
377         duration *= 2;
378     }
379
380     if (!one_in_(chance) || (has_resist_fear(player_ptr) != 0)) {
381         return;
382     }
383
384     disturb(player_ptr, false, true);
385     msg_print(_("とても暗い... とても恐い!", "It's so dark... so scary!"));
386     (void)BadStatusSetter(player_ptr).mod_fear(duration);
387 }
388
389 /*!
390  * @brief 装備による狂戦士化の発作を引き起こす
391  * @param player_ptr プレイヤー情報への参照ポインタ
392  */
393 static void curse_berserk_rage(PlayerType *player_ptr)
394 {
395     if (player_ptr->cursed.has_not(CurseTraitType::BERS_RAGE)) {
396         return;
397     }
398
399     auto *o_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::BERS_RAGE);
400     auto chance = 1500;
401     short duration = 10 + randint1(player_ptr->lev);
402
403     if (o_ptr->curse_flags.has(CurseTraitType::HEAVY_CURSE)) {
404         chance = 150;
405         duration *= 2;
406     }
407
408     if (!one_in_(chance)) {
409         return;
410     }
411
412     disturb(player_ptr, false, true);
413     msg_print(_("ウガァァア!", "RAAAAGHH!"));
414     msg_print(_("激怒の発作に襲われた!", "You feel a fit of rage coming over you!"));
415     (void)set_shero(player_ptr, duration, false);
416     (void)BadStatusSetter(player_ptr).set_fear(0);
417 }
418
419 static void curse_drain_hp(PlayerType *player_ptr)
420 {
421     if ((player_ptr->cursed.has_not(CurseTraitType::DRAIN_HP)) || !one_in_(666)) {
422         return;
423     }
424
425     const auto *item_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::DRAIN_HP);
426     const auto item_name = describe_flavor(player_ptr, item_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
427     msg_format(_("%sはあなたの体力を吸収した!", "Your %s drains HP from you!"), item_name.data());
428     take_hit(player_ptr, DAMAGE_LOSELIFE, std::min(player_ptr->lev * 2, 100), item_name);
429 }
430
431 static void curse_drain_mp(PlayerType *player_ptr)
432 {
433     if ((player_ptr->cursed.has_not(CurseTraitType::DRAIN_MANA)) || (player_ptr->csp == 0) || !one_in_(666)) {
434         return;
435     }
436
437     const auto *item_ptr = choose_cursed_obj_name(player_ptr, CurseTraitType::DRAIN_MANA);
438     const auto item_name = describe_flavor(player_ptr, item_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
439     msg_format(_("%sはあなたの魔力を吸収した!", "Your %s drains mana from you!"), item_name.data());
440     player_ptr->csp -= std::min<short>(player_ptr->lev, 50);
441     if (player_ptr->csp < 0) {
442         player_ptr->csp = 0;
443         player_ptr->csp_frac = 0;
444     }
445
446     RedrawingFlagsUpdater::get_instance().set_flag(MainWindowRedrawingFlag::MP);
447 }
448
449 static void occur_curse_effects(PlayerType *player_ptr)
450 {
451     auto is_cursed = player_ptr->cursed.has_any_of(TRC_P_FLAG_MASK);
452     is_cursed |= player_ptr->cursed_special.has_any_of(TRCS_P_FLAG_MASK);
453     if (!is_cursed || player_ptr->phase_out || player_ptr->wild_mode) {
454         return;
455     }
456
457     curse_teleport(player_ptr);
458     occur_chainsword_effect(player_ptr);
459     constexpr auto chance_ty_curse = 200;
460     if (player_ptr->cursed.has(CurseTraitType::TY_CURSE) && one_in_(chance_ty_curse)) {
461         int count = 0;
462         (void)activate_ty_curse(player_ptr, false, &count);
463     }
464
465     curse_drain_exp(player_ptr);
466     multiply_low_curse(player_ptr);
467     multiply_high_curse(player_ptr);
468     persist_curse(player_ptr);
469     curse_call_monster(player_ptr);
470     curse_cowardice(player_ptr);
471     curse_berserk_rage(player_ptr);
472     if (player_ptr->cursed.has(CurseTraitType::TELEPORT) && one_in_(200) && !player_ptr->anti_tele) {
473         disturb(player_ptr, false, true);
474         teleport_player(player_ptr, 40, TELEPORT_PASSIVE);
475     }
476
477     curse_drain_hp(player_ptr);
478     curse_drain_mp(player_ptr);
479 }
480
481 /*!
482  * @brief 10ゲームターンが進行するごとに装備効果の発動判定を行う処理
483  * / Handle curse effects once every 10 game turns
484  * @param player_ptr プレイヤーへの参照ポインタ
485  */
486 void execute_cursed_items_effect(PlayerType *player_ptr)
487 {
488     occur_curse_effects(player_ptr);
489     if (!one_in_(999) || player_ptr->anti_magic || (one_in_(2) && has_resist_curse(player_ptr))) {
490         return;
491     }
492
493     auto *o_ptr = &player_ptr->inventory_list[INVEN_LITE];
494     if (!o_ptr->is_specific_artifact(FixedArtifactId::JUDGE)) {
495         return;
496     }
497
498     if (o_ptr->is_known()) {
499         msg_print(_("『審判の宝石』はあなたの体力を吸収した!", "The Jewel of Judgement drains life from you!"));
500     } else {
501         msg_print(_("なにかがあなたの体力を吸収した!", "Something drains life from you!"));
502     }
503
504     take_hit(player_ptr, DAMAGE_LOSELIFE, std::min<short>(player_ptr->lev, 50), _("審判の宝石", "the Jewel of Judgement"));
505 }