OSDN Git Service

Merge pull request #3761 from sikabane-works/release/3.0.1.2-Beta
[hengbandforosx/hengbandosx.git] / src / player-base / player-class.cpp
1 /*!
2  * @brief プレイヤーの職業クラスに基づく耐性・能力の判定処理等を行うクラス
3  * @date 2021/09/08
4  * @author Hourier
5  * @details PlayerRaceからPlayerClassへの依存はあるが、逆は依存させないこと.
6  */
7 #include "player-base/player-class.h"
8 #include "cmd-io/diary-subtitle-table.h"
9 #include "inventory/inventory-slot-types.h"
10 #include "mind/mind-elementalist.h"
11 #include "player-info/bard-data-type.h"
12 #include "player-info/bluemage-data-type.h"
13 #include "player-info/equipment-info.h"
14 #include "player-info/force-trainer-data-type.h"
15 #include "player-info/magic-eater-data-type.h"
16 #include "player-info/mane-data-type.h"
17 #include "player-info/monk-data-type.h"
18 #include "player-info/ninja-data-type.h"
19 #include "player-info/samurai-data-type.h"
20 #include "player-info/smith-data-type.h"
21 #include "player-info/sniper-data-type.h"
22 #include "player-info/spell-hex-data-type.h"
23 #include "player/attack-defense-types.h"
24 #include "player/player-status-flags.h"
25 #include "player/special-defense-types.h"
26 #include "realm/realm-types.h"
27 #include "status/action-setter.h"
28 #include "system/item-entity.h"
29 #include "system/player-type-definition.h"
30 #include "system/redrawing-flags-updater.h"
31 #include "timed-effect/player-blindness.h"
32 #include "timed-effect/timed-effects.h"
33 #include "util/bit-flags-calculator.h"
34
35 PlayerClass::PlayerClass(PlayerType *player_ptr)
36     : player_ptr(player_ptr)
37 {
38 }
39
40 bool PlayerClass::equals(PlayerClassType type) const
41 {
42     return this->player_ptr->pclass == type;
43 }
44
45 /*!
46  * @brief プレイヤーの職業により得られる特性フラグを返す
47  */
48 TrFlags PlayerClass::tr_flags() const
49 {
50     TrFlags flags;
51     const auto plev = this->player_ptr->lev;
52
53     switch (this->player_ptr->pclass) {
54     case PlayerClassType::WARRIOR: {
55         if (plev > 29) {
56             flags.set(TR_RES_FEAR);
57         }
58         if (plev > 44) {
59             flags.set(TR_REGEN);
60         }
61
62         break;
63     }
64     case PlayerClassType::SAMURAI: {
65         if (plev > 29) {
66             flags.set(TR_RES_FEAR);
67         }
68
69         break;
70     }
71     case PlayerClassType::PALADIN: {
72         if (plev > 39) {
73             flags.set(TR_RES_FEAR);
74         }
75
76         break;
77     }
78     case PlayerClassType::CHAOS_WARRIOR: {
79         if (plev > 29) {
80             flags.set(TR_RES_CHAOS);
81         }
82         if (plev > 39) {
83             flags.set(TR_RES_FEAR);
84         }
85
86         break;
87     }
88     case PlayerClassType::MONK:
89     case PlayerClassType::FORCETRAINER: {
90         if ((plev > 9) && !heavy_armor(this->player_ptr)) {
91             flags.set(TR_SPEED);
92         }
93         if ((plev > 24) && !heavy_armor(this->player_ptr)) {
94             flags.set(TR_FREE_ACT);
95         }
96
97         break;
98     }
99     case PlayerClassType::NINJA: {
100         if (heavy_armor(this->player_ptr)) {
101             flags.set(TR_SPEED);
102         } else {
103             if (this->has_ninja_speed()) {
104                 flags.set(TR_SPEED);
105             }
106
107             if (plev > 24 && !this->player_ptr->is_icky_wield[0] && !this->player_ptr->is_icky_wield[1]) {
108                 flags.set(TR_FREE_ACT);
109             }
110         }
111
112         flags.set(TR_SLOW_DIGEST);
113         flags.set(TR_RES_FEAR);
114         if (plev > 19) {
115             flags.set(TR_RES_POIS);
116         }
117         if (plev > 24) {
118             flags.set(TR_SUST_DEX);
119         }
120         if (plev > 29) {
121             flags.set(TR_SEE_INVIS);
122         }
123
124         break;
125     }
126     case PlayerClassType::MINDCRAFTER: {
127         if (plev > 9) {
128             flags.set(TR_RES_FEAR);
129         }
130         if (plev > 19) {
131             flags.set(TR_SUST_WIS);
132         }
133         if (plev > 29) {
134             flags.set(TR_RES_CONF);
135         }
136         if (plev > 39) {
137             flags.set(TR_TELEPATHY);
138         }
139
140         break;
141     }
142     case PlayerClassType::BARD: {
143         flags.set(TR_RES_SOUND);
144         break;
145     }
146     case PlayerClassType::BERSERKER: {
147         flags.set(TR_SUST_STR);
148         flags.set(TR_SUST_DEX);
149         flags.set(TR_SUST_CON);
150         flags.set(TR_REGEN);
151         flags.set(TR_FREE_ACT);
152         flags.set(TR_SPEED);
153         if (plev > 39) {
154             flags.set(TR_REFLECT);
155         }
156
157         break;
158     }
159     case PlayerClassType::MIRROR_MASTER: {
160         if (plev > 39) {
161             flags.set(TR_REFLECT);
162         }
163
164         break;
165     }
166     case PlayerClassType::ELEMENTALIST:
167         if (has_element_resist(this->player_ptr, ElementRealmType::FIRE, 1)) {
168             flags.set(TR_RES_FIRE);
169         }
170         if (has_element_resist(this->player_ptr, ElementRealmType::FIRE, 30)) {
171             flags.set(TR_IM_FIRE);
172         }
173         if (has_element_resist(this->player_ptr, ElementRealmType::ICE, 1)) {
174             flags.set(TR_RES_COLD);
175         }
176         if (has_element_resist(this->player_ptr, ElementRealmType::ICE, 30)) {
177             flags.set(TR_IM_COLD);
178         }
179         if (has_element_resist(this->player_ptr, ElementRealmType::SKY, 1)) {
180             flags.set(TR_RES_ELEC);
181         }
182         if (has_element_resist(this->player_ptr, ElementRealmType::SKY, 30)) {
183             flags.set(TR_IM_ELEC);
184         }
185         if (has_element_resist(this->player_ptr, ElementRealmType::SEA, 1)) {
186             flags.set(TR_RES_ACID);
187         }
188         if (has_element_resist(this->player_ptr, ElementRealmType::SEA, 30)) {
189             flags.set(TR_IM_ACID);
190         }
191         if (has_element_resist(this->player_ptr, ElementRealmType::DARKNESS, 1)) {
192             flags.set(TR_RES_DARK);
193         }
194         if (has_element_resist(this->player_ptr, ElementRealmType::DARKNESS, 30)) {
195             flags.set(TR_RES_NETHER);
196         }
197         if (has_element_resist(this->player_ptr, ElementRealmType::CHAOS, 1)) {
198             flags.set(TR_RES_CONF);
199         }
200         if (has_element_resist(this->player_ptr, ElementRealmType::CHAOS, 30)) {
201             flags.set(TR_RES_CHAOS);
202         }
203         if (has_element_resist(this->player_ptr, ElementRealmType::EARTH, 1)) {
204             flags.set(TR_RES_SHARDS);
205         }
206         if (has_element_resist(this->player_ptr, ElementRealmType::EARTH, 30)) {
207             flags.set(TR_REFLECT);
208         }
209         if (has_element_resist(this->player_ptr, ElementRealmType::DEATH, 1)) {
210             flags.set(TR_RES_POIS);
211         }
212         if (has_element_resist(this->player_ptr, ElementRealmType::DEATH, 30)) {
213             flags.set(TR_RES_DISEN);
214         }
215         break;
216     default:
217         break;
218     }
219
220     return flags;
221 }
222
223 TrFlags PlayerClass::stance_tr_flags() const
224 {
225     TrFlags flags;
226
227     switch (this->get_samurai_stance()) {
228     case SamuraiStanceType::FUUJIN:
229         if (!this->player_ptr->effects()->blindness()->is_blind()) {
230             flags.set(TR_REFLECT);
231         }
232         break;
233     case SamuraiStanceType::MUSOU:
234         flags.set({ TR_RES_ACID, TR_RES_ELEC, TR_RES_FIRE, TR_RES_COLD, TR_RES_POIS });
235         flags.set({ TR_REFLECT, TR_RES_FEAR, TR_RES_LITE, TR_RES_DARK, TR_RES_BLIND, TR_RES_CONF,
236             TR_RES_SOUND, TR_RES_SHARDS, TR_RES_NETHER, TR_RES_NEXUS, TR_RES_CHAOS, TR_RES_DISEN,
237             TR_RES_WATER, TR_RES_TIME, TR_RES_CURSE });
238         flags.set({ TR_HOLD_EXP, TR_FREE_ACT, TR_LEVITATION, TR_LITE_1, TR_SEE_INVIS, TR_TELEPATHY, TR_SLOW_DIGEST, TR_REGEN });
239         flags.set({ TR_SH_FIRE, TR_SH_ELEC, TR_SH_COLD });
240         flags.set({ TR_SUST_STR, TR_SUST_INT, TR_SUST_WIS, TR_SUST_DEX, TR_SUST_CON, TR_SUST_CHR });
241         break;
242     case SamuraiStanceType::KOUKIJIN:
243         flags.set({ TR_VUL_ACID, TR_VUL_ELEC, TR_VUL_FIRE, TR_VUL_COLD });
244         break;
245     default:
246         break;
247     }
248
249     switch (this->get_monk_stance()) {
250     case MonkStanceType::GENBU:
251         flags.set(TR_REFLECT);
252         break;
253     case MonkStanceType::SUZAKU:
254         flags.set(TR_LEVITATION);
255         break;
256     case MonkStanceType::SEIRYU:
257         flags.set({ TR_RES_ACID, TR_RES_ELEC, TR_RES_FIRE, TR_RES_COLD, TR_RES_POIS });
258         flags.set({ TR_SH_FIRE, TR_SH_ELEC, TR_SH_COLD });
259         flags.set(TR_LEVITATION);
260         break;
261     default:
262         break;
263     }
264
265     return flags;
266 }
267
268 bool PlayerClass::has_stun_immunity() const
269 {
270     return this->equals(PlayerClassType::BERSERKER) && (this->player_ptr->lev > 34);
271 }
272
273 bool PlayerClass::has_poison_resistance() const
274 {
275     return this->equals(PlayerClassType::NINJA) && (this->player_ptr->lev > 44);
276 }
277
278 /*!
279  * @brief 加速ボーナスのある種族かを返す
280  * @return 加速ボーナスのある種族か否か
281  * @details
282  * 種族と職業の両方で特性による加速が得られる場合、重複して加速することはない.
283  * 代りに経験値補正が軽くなる.
284  */
285 bool PlayerClass::has_additional_speed() const
286 {
287     auto has_additional_speed = this->equals(PlayerClassType::MONK);
288     has_additional_speed |= this->equals(PlayerClassType::FORCETRAINER);
289     has_additional_speed |= this->equals(PlayerClassType::NINJA);
290     return has_additional_speed;
291 }
292
293 bool PlayerClass::is_soldier() const
294 {
295     auto is_soldier = this->equals(PlayerClassType::WARRIOR);
296     is_soldier |= this->equals(PlayerClassType::ARCHER);
297     is_soldier |= this->equals(PlayerClassType::CAVALRY);
298     is_soldier |= this->equals(PlayerClassType::BERSERKER);
299     is_soldier |= this->equals(PlayerClassType::SMITH);
300     return is_soldier;
301 }
302
303 bool PlayerClass::is_wizard() const
304 {
305     auto is_wizard = this->equals(PlayerClassType::MAGE);
306     is_wizard |= this->equals(PlayerClassType::HIGH_MAGE);
307     is_wizard |= this->equals(PlayerClassType::SORCERER);
308     is_wizard |= this->equals(PlayerClassType::MAGIC_EATER);
309     is_wizard |= this->equals(PlayerClassType::BLUE_MAGE);
310     is_wizard |= this->equals(PlayerClassType::ELEMENTALIST);
311     return is_wizard;
312 }
313
314 bool PlayerClass::is_tamer() const
315 {
316     auto is_tamer = this->equals(PlayerClassType::BEASTMASTER);
317     is_tamer |= this->equals(PlayerClassType::CAVALRY);
318     return is_tamer;
319 }
320
321 bool PlayerClass::can_browse() const
322 {
323     auto can_browse = this->equals(PlayerClassType::MINDCRAFTER);
324     can_browse |= this->equals(PlayerClassType::BERSERKER);
325     can_browse |= this->equals(PlayerClassType::NINJA);
326     can_browse |= this->equals(PlayerClassType::MIRROR_MASTER);
327     return can_browse;
328 }
329
330 bool PlayerClass::has_listed_magics() const
331 {
332     auto has_listed_magics = this->can_browse();
333     has_listed_magics |= this->equals(PlayerClassType::FORCETRAINER);
334     has_listed_magics |= this->equals(PlayerClassType::ELEMENTALIST);
335     return has_listed_magics;
336 }
337
338 /*!
339  * @brief プレイ日記のタイトルが「最高の肉体を求めて」になり得るクラスを判定する
340  * @return 該当のクラスか否か
341  */
342 bool PlayerClass::is_tough() const
343 {
344     auto is_tough = this->equals(PlayerClassType::WARRIOR);
345     is_tough |= this->equals(PlayerClassType::MONK);
346     is_tough |= this->equals(PlayerClassType::SAMURAI);
347     is_tough |= this->equals(PlayerClassType::BERSERKER);
348     return is_tough;
349 }
350
351 bool PlayerClass::is_martial_arts_pro() const
352 {
353     auto is_martial_arts_pro = this->equals(PlayerClassType::MONK);
354     is_martial_arts_pro |= this->equals(PlayerClassType::FORCETRAINER);
355     return is_martial_arts_pro;
356 }
357
358 bool PlayerClass::is_every_magic() const
359 {
360     auto is_every_magic = this->equals(PlayerClassType::SORCERER);
361     is_every_magic |= this->equals(PlayerClassType::RED_MAGE);
362     return is_every_magic;
363 }
364
365 /*!
366  * @brief 「覚えた呪文の数」という概念を持つクラスかをチェックする.
367  * @return 呪文の数を持つか否か
368  */
369 bool PlayerClass::has_number_of_spells_learned() const
370 {
371     auto has_number_of_spells_learned = this->equals(PlayerClassType::MAGE);
372     has_number_of_spells_learned |= this->equals(PlayerClassType::PRIEST);
373     has_number_of_spells_learned |= this->equals(PlayerClassType::ROGUE);
374     has_number_of_spells_learned |= this->equals(PlayerClassType::RANGER);
375     has_number_of_spells_learned |= this->equals(PlayerClassType::PALADIN);
376     has_number_of_spells_learned |= this->equals(PlayerClassType::WARRIOR_MAGE);
377     has_number_of_spells_learned |= this->equals(PlayerClassType::CHAOS_WARRIOR);
378     has_number_of_spells_learned |= this->equals(PlayerClassType::MONK);
379     has_number_of_spells_learned |= this->equals(PlayerClassType::HIGH_MAGE);
380     has_number_of_spells_learned |= this->equals(PlayerClassType::TOURIST);
381     has_number_of_spells_learned |= this->equals(PlayerClassType::BEASTMASTER);
382     has_number_of_spells_learned |= this->equals(PlayerClassType::BARD);
383     has_number_of_spells_learned |= this->equals(PlayerClassType::FORCETRAINER);
384     return has_number_of_spells_learned;
385 }
386
387 bool PlayerClass::lose_balance()
388 {
389     if (this->player_ptr->pclass != PlayerClassType::SAMURAI) {
390         return false;
391     }
392
393     if (this->samurai_stance_is(SamuraiStanceType::NONE)) {
394         return false;
395     }
396
397     this->set_samurai_stance(SamuraiStanceType::NONE);
398     auto &rfu = RedrawingFlagsUpdater::get_instance();
399     static constexpr auto flags_srf = {
400         StatusRecalculatingFlag::BONUS,
401         StatusRecalculatingFlag::MONSTER_STATUSES,
402     };
403     rfu.set_flags(flags_srf);
404     static constexpr auto flags_mwrf = {
405         MainWindowRedrawingFlag::ACTION,
406         MainWindowRedrawingFlag::TIMED_EFFECT,
407     };
408     rfu.set_flags(flags_mwrf);
409     this->player_ptr->action = ACTION_NONE;
410     return true;
411 }
412
413 SamuraiStanceType PlayerClass::get_samurai_stance() const
414 {
415     auto samurai_data = this->get_specific_data<samurai_data_type>();
416     if (!samurai_data) {
417         return SamuraiStanceType::NONE;
418     }
419
420     return samurai_data->stance;
421 }
422
423 bool PlayerClass::samurai_stance_is(SamuraiStanceType stance) const
424 {
425     return this->get_samurai_stance() == stance;
426 }
427
428 /**
429  * @brief 剣術家の型を崩す
430  *
431  * @param stance_list 崩す型を指定する。取っている型が指定された型に含まれない場合は崩さない。
432  */
433 void PlayerClass::break_samurai_stance(std::initializer_list<SamuraiStanceType> stance_list)
434 {
435     auto samurai_data = this->get_specific_data<samurai_data_type>();
436     if (!samurai_data) {
437         return;
438     }
439
440     for (auto stance : stance_list) {
441         if (samurai_data->stance == stance) {
442             set_action(player_ptr, ACTION_NONE);
443             samurai_data->stance = SamuraiStanceType::NONE;
444             break;
445         }
446     }
447 }
448
449 void PlayerClass::set_samurai_stance(SamuraiStanceType stance) const
450 {
451     auto samurai_data = this->get_specific_data<samurai_data_type>();
452     if (!samurai_data) {
453         return;
454     }
455
456     samurai_data->stance = stance;
457 }
458
459 MonkStanceType PlayerClass::get_monk_stance() const
460 {
461     auto monk_data = this->get_specific_data<monk_data_type>();
462     if (!monk_data) {
463         return MonkStanceType::NONE;
464     }
465
466     return monk_data->stance;
467 }
468
469 bool PlayerClass::monk_stance_is(MonkStanceType stance) const
470 {
471     return this->get_monk_stance() == stance;
472 }
473
474 void PlayerClass::set_monk_stance(MonkStanceType stance) const
475 {
476     auto monk_data = this->get_specific_data<monk_data_type>();
477     if (!monk_data) {
478         return;
479     }
480
481     monk_data->stance = stance;
482 }
483
484 /**
485  * @brief プレイヤーの職業にで使用する職業固有データ領域を初期化する
486  * @details 事前条件: PlayerType の職業や領域が決定済みであること
487  */
488 void PlayerClass::init_specific_data()
489 {
490     switch (this->player_ptr->pclass) {
491     case PlayerClassType::SMITH:
492         this->player_ptr->class_specific_data = std::make_shared<smith_data_type>();
493         break;
494     case PlayerClassType::FORCETRAINER:
495         this->player_ptr->class_specific_data = std::make_shared<force_trainer_data_type>();
496         break;
497     case PlayerClassType::BLUE_MAGE:
498         this->player_ptr->class_specific_data = std::make_shared<bluemage_data_type>();
499         break;
500     case PlayerClassType::MAGIC_EATER:
501         this->player_ptr->class_specific_data = std::make_shared<magic_eater_data_type>();
502         break;
503     case PlayerClassType::BARD:
504         this->player_ptr->class_specific_data = std::make_shared<bard_data_type>();
505         break;
506     case PlayerClassType::IMITATOR:
507         this->player_ptr->class_specific_data = std::make_shared<mane_data_type>();
508         break;
509     case PlayerClassType::SNIPER:
510         this->player_ptr->class_specific_data = std::make_shared<SniperData>();
511         break;
512     case PlayerClassType::SAMURAI:
513         this->player_ptr->class_specific_data = std::make_shared<samurai_data_type>();
514         break;
515     case PlayerClassType::MONK:
516         this->player_ptr->class_specific_data = std::make_shared<monk_data_type>();
517         break;
518     case PlayerClassType::NINJA:
519         this->player_ptr->class_specific_data = std::make_shared<ninja_data_type>();
520         break;
521     case PlayerClassType::HIGH_MAGE:
522         if (this->player_ptr->realm1 == REALM_HEX) {
523             this->player_ptr->class_specific_data = std::make_shared<spell_hex_data_type>();
524         } else {
525             this->player_ptr->class_specific_data = no_class_specific_data();
526         }
527         break;
528     default:
529         this->player_ptr->class_specific_data = no_class_specific_data();
530         break;
531     }
532 }
533
534 bool PlayerClass::has_ninja_speed() const
535 {
536     auto has_ninja_speed_main = !this->player_ptr->inventory_list[INVEN_MAIN_HAND].is_valid();
537     has_ninja_speed_main |= can_attack_with_main_hand(this->player_ptr);
538     auto has_ninja_speed_sub = !this->player_ptr->inventory_list[INVEN_SUB_HAND].is_valid();
539     has_ninja_speed_sub |= can_attack_with_sub_hand(this->player_ptr);
540     return has_ninja_speed_main && has_ninja_speed_sub;
541 }
542
543 /*!
544  * @brief 日記のサブタイトルの候補一覧を取得する
545  *
546  * 候補一覧の先頭は「最高の肉体を求めて」、末尾は「最高の頭脳を求めて」で
547  * あるため、プレイヤーの職業に従い範囲を決定する。
548  *
549  * @return 候補一覧を参照するstd::spanオブジェクト
550  */
551 std::span<const std::string> PlayerClass::get_subtitle_candidates() const
552 {
553     static const std::span<const std::string> candidates(diary_subtitles.begin(), diary_subtitles.end());
554     const auto max = diary_subtitles.size();
555     if (this->is_tough()) {
556         return candidates.subspan(0, max - 1);
557     }
558
559     if (this->is_wizard()) {
560         return candidates.subspan(1);
561     }
562
563     return candidates.subspan(1, max - 2);
564 }