2 * @brief プレイヤーの職業クラスに基づく耐性・能力の判定処理等を行うクラス
5 * @details PlayerRaceからPlayerClassへの依存はあるが、逆は依存させないこと.
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"
36 PlayerClass::PlayerClass(PlayerType *player_ptr)
37 : player_ptr(player_ptr)
41 bool PlayerClass::equals(PlayerClassType type) const
43 return this->player_ptr->pclass == type;
47 * @brief プレイヤーの職業により得られる特性フラグを返す
49 TrFlags PlayerClass::tr_flags() const
52 const auto plev = this->player_ptr->lev;
54 switch (this->player_ptr->pclass) {
55 case PlayerClassType::WARRIOR: {
57 flags.set(TR_RES_FEAR);
65 case PlayerClassType::SAMURAI: {
67 flags.set(TR_RES_FEAR);
72 case PlayerClassType::PALADIN: {
74 flags.set(TR_RES_FEAR);
79 case PlayerClassType::CHAOS_WARRIOR: {
81 flags.set(TR_RES_CHAOS);
84 flags.set(TR_RES_FEAR);
89 case PlayerClassType::MONK:
90 case PlayerClassType::FORCETRAINER: {
91 if ((plev > 9) && !heavy_armor(this->player_ptr)) {
94 if ((plev > 24) && !heavy_armor(this->player_ptr)) {
95 flags.set(TR_FREE_ACT);
100 case PlayerClassType::NINJA: {
101 if (heavy_armor(this->player_ptr)) {
104 if (this->has_ninja_speed()) {
108 if (plev > 24 && !this->player_ptr->is_icky_wield[0] && !this->player_ptr->is_icky_wield[1]) {
109 flags.set(TR_FREE_ACT);
113 flags.set(TR_SLOW_DIGEST);
114 flags.set(TR_RES_FEAR);
116 flags.set(TR_RES_POIS);
119 flags.set(TR_SUST_DEX);
122 flags.set(TR_SEE_INVIS);
127 case PlayerClassType::MINDCRAFTER: {
129 flags.set(TR_RES_FEAR);
132 flags.set(TR_SUST_WIS);
135 flags.set(TR_RES_CONF);
138 flags.set(TR_TELEPATHY);
143 case PlayerClassType::BARD: {
144 flags.set(TR_RES_SOUND);
147 case PlayerClassType::BERSERKER: {
148 flags.set(TR_SUST_STR);
149 flags.set(TR_SUST_DEX);
150 flags.set(TR_SUST_CON);
152 flags.set(TR_FREE_ACT);
155 flags.set(TR_REFLECT);
160 case PlayerClassType::MIRROR_MASTER: {
162 flags.set(TR_REFLECT);
167 case PlayerClassType::ELEMENTALIST:
168 if (has_element_resist(this->player_ptr, ElementRealmType::FIRE, 1)) {
169 flags.set(TR_RES_FIRE);
171 if (has_element_resist(this->player_ptr, ElementRealmType::FIRE, 30)) {
172 flags.set(TR_IM_FIRE);
174 if (has_element_resist(this->player_ptr, ElementRealmType::ICE, 1)) {
175 flags.set(TR_RES_COLD);
177 if (has_element_resist(this->player_ptr, ElementRealmType::ICE, 30)) {
178 flags.set(TR_IM_COLD);
180 if (has_element_resist(this->player_ptr, ElementRealmType::SKY, 1)) {
181 flags.set(TR_RES_ELEC);
183 if (has_element_resist(this->player_ptr, ElementRealmType::SKY, 30)) {
184 flags.set(TR_IM_ELEC);
186 if (has_element_resist(this->player_ptr, ElementRealmType::SEA, 1)) {
187 flags.set(TR_RES_ACID);
189 if (has_element_resist(this->player_ptr, ElementRealmType::SEA, 30)) {
190 flags.set(TR_IM_ACID);
192 if (has_element_resist(this->player_ptr, ElementRealmType::DARKNESS, 1)) {
193 flags.set(TR_RES_DARK);
195 if (has_element_resist(this->player_ptr, ElementRealmType::DARKNESS, 30)) {
196 flags.set(TR_RES_NETHER);
198 if (has_element_resist(this->player_ptr, ElementRealmType::CHAOS, 1)) {
199 flags.set(TR_RES_CONF);
201 if (has_element_resist(this->player_ptr, ElementRealmType::CHAOS, 30)) {
202 flags.set(TR_RES_CHAOS);
204 if (has_element_resist(this->player_ptr, ElementRealmType::EARTH, 1)) {
205 flags.set(TR_RES_SHARDS);
207 if (has_element_resist(this->player_ptr, ElementRealmType::EARTH, 30)) {
208 flags.set(TR_REFLECT);
210 if (has_element_resist(this->player_ptr, ElementRealmType::DEATH, 1)) {
211 flags.set(TR_RES_POIS);
213 if (has_element_resist(this->player_ptr, ElementRealmType::DEATH, 30)) {
214 flags.set(TR_RES_DISEN);
224 TrFlags PlayerClass::stance_tr_flags() const
228 switch (this->get_samurai_stance()) {
229 case SamuraiStanceType::FUUJIN:
230 if (!this->player_ptr->effects()->blindness()->is_blind()) {
231 flags.set(TR_REFLECT);
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 });
243 case SamuraiStanceType::KOUKIJIN:
244 flags.set({ TR_VUL_ACID, TR_VUL_ELEC, TR_VUL_FIRE, TR_VUL_COLD });
250 switch (this->get_monk_stance()) {
251 case MonkStanceType::GENBU:
252 flags.set(TR_REFLECT);
254 case MonkStanceType::SUZAKU:
255 flags.set(TR_LEVITATION);
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);
269 bool PlayerClass::has_stun_immunity() const
271 return this->equals(PlayerClassType::BERSERKER) && (this->player_ptr->lev > 34);
274 bool PlayerClass::has_poison_resistance() const
276 return this->equals(PlayerClassType::NINJA) && (this->player_ptr->lev > 44);
280 * @brief 加速ボーナスのある種族かを返す
281 * @return 加速ボーナスのある種族か否か
283 * 種族と職業の両方で特性による加速が得られる場合、重複して加速することはない.
286 bool PlayerClass::has_additional_speed() const
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;
294 bool PlayerClass::is_soldier() const
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);
304 bool PlayerClass::is_wizard() const
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);
315 bool PlayerClass::is_tamer() const
317 auto is_tamer = this->equals(PlayerClassType::BEASTMASTER);
318 is_tamer |= this->equals(PlayerClassType::CAVALRY);
322 bool PlayerClass::can_browse() const
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);
331 bool PlayerClass::has_listed_magics() const
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;
340 * @brief プレイ日記のタイトルが「最高の肉体を求めて」になり得るクラスを判定する
343 bool PlayerClass::is_tough() const
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);
352 bool PlayerClass::is_martial_arts_pro() const
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;
359 bool PlayerClass::is_every_magic() const
361 auto is_every_magic = this->equals(PlayerClassType::SORCERER);
362 is_every_magic |= this->equals(PlayerClassType::RED_MAGE);
363 return is_every_magic;
367 * @brief 「覚えた呪文の数」という概念を持つクラスかをチェックする.
370 bool PlayerClass::has_number_of_spells_learned() const
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;
388 bool PlayerClass::lose_balance()
390 if (this->player_ptr->pclass != PlayerClassType::SAMURAI) {
394 if (this->samurai_stance_is(SamuraiStanceType::NONE)) {
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,
404 rfu.set_flags(flags_srf);
405 const auto flags_mwrf = {
406 MainWindowRedrawingFlag::ACTION,
407 MainWindowRedrawingFlag::TIMED_EFFECT,
409 rfu.set_flags(flags_mwrf);
410 this->player_ptr->action = ACTION_NONE;
414 SamuraiStanceType PlayerClass::get_samurai_stance() const
416 auto samurai_data = this->get_specific_data<samurai_data_type>();
418 return SamuraiStanceType::NONE;
421 return samurai_data->stance;
424 bool PlayerClass::samurai_stance_is(SamuraiStanceType stance) const
426 return this->get_samurai_stance() == stance;
432 * @param stance_list 崩す型を指定する。取っている型が指定された型に含まれない場合は崩さない。
434 void PlayerClass::break_samurai_stance(std::initializer_list<SamuraiStanceType> stance_list)
436 auto samurai_data = this->get_specific_data<samurai_data_type>();
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;
450 void PlayerClass::set_samurai_stance(SamuraiStanceType stance) const
452 auto samurai_data = this->get_specific_data<samurai_data_type>();
457 samurai_data->stance = stance;
460 MonkStanceType PlayerClass::get_monk_stance() const
462 auto monk_data = this->get_specific_data<monk_data_type>();
464 return MonkStanceType::NONE;
467 return monk_data->stance;
470 bool PlayerClass::monk_stance_is(MonkStanceType stance) const
472 return this->get_monk_stance() == stance;
475 void PlayerClass::set_monk_stance(MonkStanceType stance) const
477 auto monk_data = this->get_specific_data<monk_data_type>();
482 monk_data->stance = stance;
486 * @brief プレイヤーの職業にで使用する職業固有データ領域を初期化する
487 * @details 事前条件: PlayerType の職業や領域が決定済みであること
489 void PlayerClass::init_specific_data()
491 switch (this->player_ptr->pclass) {
492 case PlayerClassType::SMITH:
493 this->player_ptr->class_specific_data = std::make_shared<smith_data_type>();
495 case PlayerClassType::FORCETRAINER:
496 this->player_ptr->class_specific_data = std::make_shared<force_trainer_data_type>();
498 case PlayerClassType::BLUE_MAGE:
499 this->player_ptr->class_specific_data = std::make_shared<bluemage_data_type>();
501 case PlayerClassType::MAGIC_EATER:
502 this->player_ptr->class_specific_data = std::make_shared<magic_eater_data_type>();
504 case PlayerClassType::BARD:
505 this->player_ptr->class_specific_data = std::make_shared<bard_data_type>();
507 case PlayerClassType::IMITATOR:
508 this->player_ptr->class_specific_data = std::make_shared<mane_data_type>();
510 case PlayerClassType::SNIPER:
511 this->player_ptr->class_specific_data = std::make_shared<sniper_data_type>();
513 case PlayerClassType::SAMURAI:
514 this->player_ptr->class_specific_data = std::make_shared<samurai_data_type>();
516 case PlayerClassType::MONK:
517 this->player_ptr->class_specific_data = std::make_shared<monk_data_type>();
519 case PlayerClassType::NINJA:
520 this->player_ptr->class_specific_data = std::make_shared<ninja_data_type>();
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>();
526 this->player_ptr->class_specific_data = no_class_specific_data();
530 this->player_ptr->class_specific_data = no_class_specific_data();
535 bool PlayerClass::has_ninja_speed() const
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;
545 * @brief 日記のサブタイトルの候補一覧を取得する
547 * 候補一覧の先頭は「最高の肉体を求めて」、末尾は「最高の頭脳を求めて」で
548 * あるため、プレイヤーの職業に従い範囲を決定する。
550 * @return 候補一覧を参照するstd::spanオブジェクト
552 std::span<const std::string> PlayerClass::get_subtitle_candidates() const
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);
560 if (this->is_wizard()) {
561 return candidates.subspan(1);
564 return candidates.subspan(1, max - 2);