OSDN Git Service

Merge pull request #3814 from Slimebreath6078/feature/Add_Laffey_II
[hengbandforosx/hengbandosx.git] / src / main / scene-table-monster.cpp
1 /*!
2  * @file scene-table-monster.cpp
3  * @brief モンスターの遭遇状況に応じたBGM設定処理実装
4  */
5
6 #include "main/scene-table-monster.h"
7 #include "dungeon/quest.h"
8 #include "main/music-definitions-table.h"
9 #include "monster-race/monster-race.h"
10 #include "system/floor-type-definition.h"
11 #include "system/monster-entity.h"
12 #include "system/monster-race-info.h"
13 #include "system/player-type-definition.h"
14 #include "util/bit-flags-calculator.h"
15 #include "world/world.h"
16
17 struct scene_monster_info {
18     MONSTER_IDX m_idx;
19     MonsterRaceInfo *ap_r_ptr;
20     GAME_TURN last_seen; //!< 最後に対象モンスター見たゲームターン
21     uint32_t mute_until; //!< この時間に到達するまでモンスターBGMは設定しない
22 };
23
24 scene_monster_info scene_target_monster;
25
26 inline static bool has_shadower_flag(MonsterEntity *m_ptr)
27 {
28     return m_ptr->mflag2.has(MonsterConstantFlagType::KAGE);
29 }
30
31 inline static bool is_unique(MonsterRaceInfo *ap_r_ptr)
32 {
33     return ap_r_ptr->kind_flags.has(MonsterKindType::UNIQUE);
34 }
35
36 inline static bool is_unknown_monster(MonsterRaceInfo *ap_r_ptr)
37 {
38     return ap_r_ptr->r_tkills == 0;
39 }
40
41 void clear_scene_target_monster()
42 {
43     scene_target_monster.ap_r_ptr = nullptr;
44 }
45
46 static GAME_TURN get_game_turn()
47 {
48     GAME_TURN ret = w_ptr->game_turn;
49     if (ret == w_ptr->game_turn_limit) {
50         ret = 0;
51     }
52     return ret;
53 }
54
55 /*!
56  * @brief モンスターBGMの制限期間を設定する
57  * @details 指定の時間が経過するまでモンスターBGMの再生を制限する
58  * @param msec 制限する時間(秒)
59  */
60 void set_temp_mute_scene_monster(int sec)
61 {
62     scene_target_monster.mute_until = (uint32_t)time(nullptr) + sec;
63 }
64
65 /*!
66  * @brief モンスターBGMの制限期間か判定する
67  * @details ダンジョンターン数がscene_target_monster.mute_untilの値になるまで制限期間。
68  * @return モンスターBGMの制限期間の場合trueを返す
69  */
70 inline static bool can_mute_scene_monster()
71 {
72     return scene_target_monster.mute_until > time(nullptr);
73 }
74
75 /*!
76  * @brief モンスターの優先判定
77  * @details ユニーク、あやしい影、未知のモンスター、レベルの高さ、モンスターIDで優先を付ける。
78  * @param player_ptr プレイヤーへの参照ポインタ
79  * @param m_idx1 モンスターA(新参)
80  * @param m_idx2 モンスターB(現対象)
81  * @retval true モンスターAが優先
82  * @retval false モンスターBが優先
83  */
84 static bool is_high_rate(PlayerType *player_ptr, MONSTER_IDX m_idx1, MONSTER_IDX m_idx2)
85 {
86     // FIXME 視界内モンスターリストの比較関数と同じ処理
87     auto floor_ptr = player_ptr->current_floor_ptr;
88     auto m_ptr1 = &floor_ptr->m_list[m_idx1];
89     auto m_ptr2 = &floor_ptr->m_list[m_idx2];
90     auto ap_r_ptr1 = &m_ptr1->get_appearance_monrace();
91     auto ap_r_ptr2 = &m_ptr2->get_appearance_monrace();
92
93     /* Unique monsters first */
94     if (ap_r_ptr1->kind_flags.has(MonsterKindType::UNIQUE) != ap_r_ptr2->kind_flags.has(MonsterKindType::UNIQUE)) {
95         return ap_r_ptr1->kind_flags.has(MonsterKindType::UNIQUE);
96     }
97
98     /* Shadowers first (あやしい影) */
99     if (m_ptr1->mflag2.has(MonsterConstantFlagType::KAGE) != m_ptr2->mflag2.has(MonsterConstantFlagType::KAGE)) {
100         return m_ptr1->mflag2.has(MonsterConstantFlagType::KAGE);
101     }
102
103     /* Unknown monsters first */
104     if ((ap_r_ptr1->r_tkills == 0) != (ap_r_ptr2->r_tkills == 0)) {
105         return ap_r_ptr1->r_tkills == 0;
106     }
107
108     /* Higher level monsters first (if known) */
109     if (ap_r_ptr1->r_tkills && ap_r_ptr2->r_tkills && ap_r_ptr1->level != ap_r_ptr2->level) {
110         return ap_r_ptr1->level > ap_r_ptr2->level;
111     }
112
113     /* Sort by index if all conditions are same */
114     return m_ptr1->ap_r_idx > m_ptr2->ap_r_idx;
115 }
116
117 /*!
118  * @brief BGM対象モンスター更新処理
119  * @details 現在の対象と対象候補が同一モンスターの場合、最後に見たゲームターン情報を更新する。
120  * 対象候補が現在の対象よりも上位であれば対象を入れ替える。
121  * ユニーク、あやしい影、未知のモンスター、レベルの高さ、モンスターIDで優先を付ける。
122  * @param player_ptr プレイヤーへの参照ポインタ
123  * @param m_idx BGM対象候補のモンスター
124  */
125 static void update_target_monster(PlayerType *player_ptr, MONSTER_IDX m_idx)
126 {
127     if (scene_target_monster.ap_r_ptr && (scene_target_monster.m_idx == m_idx)) {
128         // 同一モンスター。最後に見たゲームターンを更新。
129         scene_target_monster.last_seen = get_game_turn();
130     } else {
131         bool do_dwap = false;
132         if (!scene_target_monster.ap_r_ptr) {
133             // 空席
134             do_dwap = true;
135         } else if (is_high_rate(player_ptr, m_idx, scene_target_monster.m_idx)) {
136             // 入れ替え
137             do_dwap = true;
138         }
139
140         if (do_dwap) {
141             auto *m_ptr = &player_ptr->current_floor_ptr->m_list[m_idx];
142             auto *ap_r_ptr = &m_ptr->get_appearance_monrace();
143             scene_target_monster.m_idx = m_idx;
144             scene_target_monster.ap_r_ptr = ap_r_ptr;
145             scene_target_monster.last_seen = get_game_turn();
146         }
147     }
148 }
149
150 using scene_monster_func = bool (*)(PlayerType *player_ptr, scene_type *value);
151
152 static bool scene_monster(PlayerType *player_ptr, scene_type *value)
153 {
154     auto *m_ptr = &player_ptr->current_floor_ptr->m_list[scene_target_monster.m_idx];
155
156     if (has_shadower_flag(m_ptr)) {
157         value->type = TERM_XTRA_MUSIC_BASIC;
158         value->val = MUSIC_BASIC_SHADOWER;
159         return true;
160     } else {
161         value->type = TERM_XTRA_MUSIC_MONSTER;
162         value->val = enum2i(m_ptr->ap_r_idx);
163         return true;
164     }
165 }
166
167 static bool scene_unique(PlayerType *player_ptr, scene_type *value)
168 {
169     (void)player_ptr;
170
171     if (is_unique(scene_target_monster.ap_r_ptr)) {
172         value->type = TERM_XTRA_MUSIC_BASIC;
173         value->val = MUSIC_BASIC_UNIQUE;
174         return true;
175     }
176
177     return false;
178 }
179
180 static bool scene_unknown(PlayerType *player_ptr, scene_type *value)
181 {
182     (void)player_ptr;
183     if (is_unknown_monster(scene_target_monster.ap_r_ptr)) {
184         value->type = TERM_XTRA_MUSIC_BASIC;
185         value->val = MUSIC_BASIC_UNKNOWN_MONSTER;
186         return true;
187     }
188
189     return false;
190 }
191
192 static bool scene_high_level(PlayerType *player_ptr, scene_type *value)
193 {
194     if (!is_unknown_monster(scene_target_monster.ap_r_ptr) && (scene_target_monster.ap_r_ptr->level >= player_ptr->lev)) {
195         value->type = TERM_XTRA_MUSIC_BASIC;
196         value->val = MUSIC_BASIC_HIGHER_LEVEL_MONSTER;
197         return true;
198     }
199
200     return false;
201 }
202
203 /*! モンスターBGMのフォールバック設定。
204  * 先頭から適用する(先にある方を優先する)。
205  */
206 std::vector<scene_monster_func> scene_monster_def_list = {
207     // scene_monster : あやしい影 or モンスターID
208     scene_monster,
209     // scene_unique : ユニークモンスター判定
210     scene_unique,
211     // scene_unkown : 未知のモンスター判定
212     scene_unknown,
213     // scene_high_level : 高レベルのモンスター判定
214     scene_high_level,
215 };
216
217 int get_scene_monster_count()
218 {
219     return scene_monster_def_list.size();
220 }
221
222 /*!
223  * @brief 現在の条件でモンスターのBGM選曲をリストに設定する。
224  * @details リストのfrom_indexの位置から、get_scene_monster_count()で得られる個数分設定する。
225  * 視界内モンスターリスト先頭のモンスターを記憶し、以前のモンスターと比較してより上位のモンスターをBGM選曲の対象とする。
226  * 記憶したモンスターが視界内に存在しない場合、一定のゲームターン経過で忘れる。
227  * @param player_ptr プレイヤーへの参照ポインタ
228  * @param monster_list 視界内モンスターリスト
229  * @param list BGM選曲リスト
230  * @param from_index リストの更新開始位置
231  */
232 void refresh_scene_monster(PlayerType *player_ptr, const std::vector<MONSTER_IDX> &monster_list, scene_type_list &list, int from_index)
233 {
234     const bool mute = can_mute_scene_monster();
235
236     if (mute) {
237         // モンスターBGM制限中
238         clear_scene_target_monster();
239     } else {
240         if (scene_target_monster.ap_r_ptr) {
241             // BGM対象から外すチェック
242             if (get_game_turn() - scene_target_monster.last_seen >= 200) {
243                 // 最後に見かけてから一定のゲームターンが経過した場合、BGM対象から外す
244                 clear_scene_target_monster();
245             } else {
246                 auto *m_ptr = &player_ptr->current_floor_ptr->m_list[scene_target_monster.m_idx];
247                 auto *ap_r_ptr = &m_ptr->get_appearance_monrace();
248                 if (ap_r_ptr != scene_target_monster.ap_r_ptr) {
249                     // 死亡、チェンジモンスター、etc.
250                     clear_scene_target_monster();
251                 }
252             }
253         }
254
255         if (!monster_list.empty()) {
256             // 現在のBGM対象とモンスターリスト先頭を比較し、上位をBGM対象に設定する
257             update_target_monster(player_ptr, monster_list.front());
258         }
259     }
260
261     if (scene_target_monster.ap_r_ptr) {
262         // BGM対象の条件で選曲リストを設定する
263         for (auto func : scene_monster_def_list) {
264             scene_type &item = list[from_index];
265             if (!func(player_ptr, &item)) {
266                 // Note -- 特に定義を設けていないが、type = 0は無効な値とする。
267                 item.type = 0;
268                 item.val = 0;
269             }
270             ++from_index;
271         }
272     } else {
273         // BGM対象なしの場合は0で埋める
274         const int count = get_scene_monster_count();
275         for (int i = 0; i < count; i++) {
276             scene_type &item = list[from_index + i];
277             // Note -- 特に定義を設けていないが、type = 0は無効な値とする。
278             item.type = 0;
279             item.val = 0;
280         }
281     }
282 }