OSDN Git Service

[Refactor] #2628 Renamed object-type-definition.cpp/h to item-entity.cpp/h
[hengbandforosx/hengbandosx.git] / src / dungeon / quest-completion-checker.cpp
1 #include "dungeon/quest-completion-checker.h"
2 #include "core/player-update-types.h"
3 #include "dungeon/quest.h"
4 #include "effect/effect-characteristics.h"
5 #include "floor/cave.h"
6 #include "floor/floor-object.h"
7 #include "floor/floor-util.h"
8 #include "grid/feature-flag-types.h"
9 #include "grid/feature.h"
10 #include "grid/grid.h"
11 #include "monster-race/monster-race.h"
12 #include "monster/monster-info.h"
13 #include "object-enchant/item-apply-magic.h"
14 #include "object-enchant/object-ego.h"
15 #include "system/floor-type-definition.h"
16 #include "system/grid-type-definition.h"
17 #include "system/item-entity.h"
18 #include "system/monster-race-definition.h"
19 #include "system/monster-type-definition.h"
20 #include "system/player-type-definition.h"
21 #include "util/bit-flags-calculator.h"
22 #include "view/display-messages.h"
23 #include <algorithm>
24
25 QuestCompletionChecker::QuestCompletionChecker(PlayerType *player_ptr, MonsterEntity *m_ptr)
26     : player_ptr(player_ptr)
27     , m_ptr(m_ptr)
28 {
29 }
30
31 /*!
32  * @brief 特定の敵を倒した際にクエスト達成処理 /
33  * Check for "Quest" completion when a quest monster is killed or charmed.
34  * @param player_ptr プレイヤーへの参照ポインタ
35  * @param m_ptr 撃破したモンスターの構造体参照ポインタ
36  */
37 void QuestCompletionChecker::complete()
38 {
39     this->set_quest_idx();
40     auto create_stairs = false;
41     auto reward = false;
42     auto &quest_list = QuestList::get_instance();
43     if (inside_quest(this->quest_idx) && (quest_list[this->quest_idx].status == QuestStatusType::TAKEN)) {
44         this->q_ptr = &quest_list[this->quest_idx];
45         auto [tmp_create_stairs, tmp_reward] = this->switch_completion();
46         create_stairs = tmp_create_stairs;
47         reward = tmp_reward;
48     }
49
50     auto pos = this->make_stairs(create_stairs);
51     if (!reward) {
52         return;
53     }
54
55     this->make_reward(pos);
56 }
57
58 static bool check_quest_completion(PlayerType *player_ptr, const quest_type &q_ref, MonsterEntity *m_ptr)
59 {
60     auto *floor_ptr = player_ptr->current_floor_ptr;
61     if (q_ref.status != QuestStatusType::TAKEN) {
62         return false;
63     }
64
65     if (any_bits(q_ref.flags, QUEST_FLAG_PRESET)) {
66         return false;
67     }
68
69     if ((q_ref.level != floor_ptr->dun_level)) {
70         return false;
71     }
72
73     if ((q_ref.type == QuestKindType::FIND_ARTIFACT) || (q_ref.type == QuestKindType::FIND_EXIT)) {
74         return false;
75     }
76
77     auto kill_them_all = q_ref.type == QuestKindType::KILL_NUMBER;
78     kill_them_all |= q_ref.type == QuestKindType::TOWER;
79     kill_them_all |= q_ref.type == QuestKindType::KILL_ALL;
80     if (kill_them_all) {
81         return true;
82     }
83
84     auto is_target = (q_ref.type == QuestKindType::RANDOM) && (q_ref.r_idx == m_ptr->r_idx);
85     if ((q_ref.type == QuestKindType::KILL_LEVEL) || is_target) {
86         return true;
87     }
88
89     return false;
90 }
91
92 void QuestCompletionChecker::set_quest_idx()
93 {
94     auto *floor_ptr = this->player_ptr->current_floor_ptr;
95     const auto &quest_list = QuestList::get_instance();
96     this->quest_idx = floor_ptr->quest_number;
97     if (inside_quest(this->quest_idx)) {
98         return;
99     }
100     auto q = std::find_if(quest_list.rbegin(), quest_list.rend(), [this](auto q) { return check_quest_completion(this->player_ptr, q.second, this->m_ptr); });
101
102     if (q != quest_list.rend()) {
103         this->quest_idx = q->first;
104     } else {
105         this->quest_idx = QuestId::NONE;
106     }
107 }
108
109 std::tuple<bool, bool> QuestCompletionChecker::switch_completion()
110 {
111     switch (this->q_ptr->type) {
112     case QuestKindType::KILL_NUMBER:
113         this->complete_kill_number();
114         return std::make_tuple(false, false);
115     case QuestKindType::KILL_ALL:
116         this->complete_kill_all();
117         return std::make_tuple(false, false);
118     case QuestKindType::KILL_LEVEL:
119     case QuestKindType::RANDOM:
120         return this->complete_random();
121     case QuestKindType::TOWER:
122         this->complete_tower();
123         return std::make_tuple(false, false);
124     default:
125         return std::make_tuple(false, false);
126     }
127 }
128
129 void QuestCompletionChecker::complete_kill_number()
130 {
131     this->q_ptr->cur_num++;
132     if (this->q_ptr->cur_num >= this->q_ptr->num_mon) {
133         complete_quest(this->player_ptr, this->quest_idx);
134         this->q_ptr->cur_num = 0;
135     }
136 }
137
138 void QuestCompletionChecker::complete_kill_all()
139 {
140     if (!this->m_ptr->is_hostile() || (this->count_all_hostile_monsters() != 1)) {
141         return;
142     }
143
144     if (any_bits(this->q_ptr->flags, QUEST_FLAG_SILENT)) {
145         this->q_ptr->status = QuestStatusType::FINISHED;
146     } else {
147         complete_quest(this->player_ptr, this->quest_idx);
148     }
149 }
150
151 std::tuple<bool, bool> QuestCompletionChecker::complete_random()
152 {
153     if (this->q_ptr->r_idx != this->m_ptr->r_idx) {
154         return std::make_tuple(false, false);
155     }
156
157     this->q_ptr->cur_num++;
158     if (this->q_ptr->cur_num < this->q_ptr->max_num) {
159         return std::make_tuple(false, false);
160     }
161
162     complete_quest(this->player_ptr, this->quest_idx);
163     auto create_stairs = false;
164     if (none_bits(this->q_ptr->flags, QUEST_FLAG_PRESET)) {
165         create_stairs = true;
166         this->player_ptr->current_floor_ptr->quest_number = QuestId::NONE;
167     }
168
169     if ((this->quest_idx == QuestId::OBERON) || (this->quest_idx == QuestId::SERPENT)) {
170         this->q_ptr->status = QuestStatusType::FINISHED;
171     }
172
173     auto reward = false;
174     if (this->q_ptr->type == QuestKindType::RANDOM) {
175         reward = true;
176         this->q_ptr->status = QuestStatusType::FINISHED;
177     }
178
179     return std::make_tuple(create_stairs, reward);
180 }
181
182 void QuestCompletionChecker::complete_tower()
183 {
184     const auto &quest_list = QuestList::get_instance();
185     if (!this->m_ptr->is_hostile()) {
186         return;
187     }
188
189     if (this->count_all_hostile_monsters() != 1) {
190         return;
191     }
192
193     this->q_ptr->status = QuestStatusType::STAGE_COMPLETED;
194     auto is_tower_completed = quest_list[QuestId::TOWER1].status == QuestStatusType::STAGE_COMPLETED;
195     is_tower_completed &= quest_list[QuestId::TOWER2].status == QuestStatusType::STAGE_COMPLETED;
196     is_tower_completed &= quest_list[QuestId::TOWER3].status == QuestStatusType::STAGE_COMPLETED;
197     if (is_tower_completed) {
198         complete_quest(this->player_ptr, QuestId::TOWER1);
199     }
200 }
201
202 /*!
203  * @brief 現在フロアに残っている敵モンスターの数を返す /
204  * @return 現在の敵モンスターの数
205  */
206 int QuestCompletionChecker::count_all_hostile_monsters()
207 {
208     auto *floor_ptr = this->player_ptr->current_floor_ptr;
209     auto number_mon = 0;
210     for (auto x = 0; x < floor_ptr->width; ++x) {
211         for (auto y = 0; y < floor_ptr->height; ++y) {
212             auto m_idx = floor_ptr->grid_array[y][x].m_idx;
213             if ((m_idx > 0) && floor_ptr->m_list[m_idx].is_hostile()) {
214                 ++number_mon;
215             }
216         }
217     }
218
219     return number_mon;
220 }
221
222 Pos2D QuestCompletionChecker::make_stairs(const bool create_stairs)
223 {
224     auto y = this->m_ptr->fy;
225     auto x = this->m_ptr->fx;
226     if (!create_stairs) {
227         return Pos2D(y, x);
228     }
229
230     auto *floor_ptr = this->player_ptr->current_floor_ptr;
231     auto *g_ptr = &floor_ptr->grid_array[y][x];
232     while (cave_has_flag_bold(floor_ptr, y, x, TerrainCharacteristics::PERMANENT) || !g_ptr->o_idx_list.empty() || g_ptr->is_object()) {
233         int ny;
234         int nx;
235         scatter(this->player_ptr, &ny, &nx, y, x, 1, PROJECT_NONE);
236         y = ny;
237         x = nx;
238         g_ptr = &floor_ptr->grid_array[y][x];
239     }
240
241     msg_print(_("魔法の階段が現れた...", "A magical staircase appears..."));
242     cave_set_feat(this->player_ptr, y, x, feat_down_stair);
243     set_bits(this->player_ptr->update, PU_FLOW);
244     return Pos2D(y, x);
245 }
246
247 void QuestCompletionChecker::make_reward(const Pos2D pos)
248 {
249     auto dun_level = this->player_ptr->current_floor_ptr->dun_level;
250     for (auto i = 0; i < (dun_level / 15) + 1; i++) {
251         ItemEntity item;
252         while (true) {
253             item.wipe();
254             auto &r_ref = monraces_info[this->m_ptr->r_idx];
255             (void)make_object(this->player_ptr, &item, AM_GOOD | AM_GREAT, r_ref.level);
256             if (!this->check_quality(item)) {
257                 continue;
258             }
259
260             (void)drop_near(this->player_ptr, &item, -1, pos.y, pos.x);
261             break;
262         }
263     }
264 }
265
266 /*!
267  * @brief ランダムクエスト報酬の品質をチェックする
268  * @param item 生成された高級品への参照
269  * @return 十分な品質か否か
270  * @details 以下のものを弾く
271  * 1. 呪われれた装備品
272  * 2. 固定アーティファクト以外の矢弾
273  * 3. 穴掘りエゴの装備品
274  */
275 bool QuestCompletionChecker::check_quality(ItemEntity &item)
276 {
277     auto is_good_reward = !item.is_cursed();
278     is_good_reward &= !item.is_ammo() || (item.is_ammo() && item.is_fixed_artifact());
279     is_good_reward &= item.ego_idx != EgoType::DIGGING;
280     return is_good_reward;
281 }