OSDN Git Service

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