1 /**
2 * @file
3 * @brief player methods dealing with mesmerisation.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "mpr.h"
9 #include "player.h"
10
11 #include "areas.h"
12 #include "art-enum.h"
13 #include "coord.h"
14 #include "env.h"
15 #include "fprop.h"
16 #include "losglobal.h"
17 #include "state.h"
18
19 // Add a monster to the list of beholders.
add_beholder(const monster & mon,bool axe)20 void player::add_beholder(const monster& mon, bool axe)
21 {
22 if (is_sanctuary(pos()) && !axe)
23 {
24 if (mons_is_siren_beholder(mon))
25 {
26 if (can_see(mon))
27 {
28 mprf("%s's singing sounds muted, and has no effect on you.",
29 mon.name(DESC_THE).c_str());
30 }
31 else
32 mpr("The melody is strangely muted, and has no effect on you.");
33 }
34 else
35 {
36 if (can_see(mon))
37 mprf("%s's is no longer quite as mesmerising!", mon.name(DESC_THE).c_str());
38 else
39 mpr("Your mesmeriser suddenly seems less interesting!");
40 }
41
42 return;
43 }
44
45 if (!duration[DUR_MESMERISED])
46 {
47 set_duration(DUR_MESMERISED, random_range(7, 15), 15);
48 beholders.push_back(mon.mid);
49 if (!axe)
50 {
51 mprf(MSGCH_WARN, "You are mesmerised by %s!",
52 mon.name(DESC_THE).c_str());
53 }
54 }
55 else
56 {
57 increase_duration(DUR_MESMERISED, random_range(5, 8), 15);
58 if (!beheld_by(mon))
59 beholders.push_back(mon.mid);
60 }
61 }
62
63 // Whether player is mesmerised.
beheld() const64 bool player::beheld() const
65 {
66 ASSERT((duration[DUR_MESMERISED] > 0) == !beholders.empty());
67 return duration[DUR_MESMERISED] > 0;
68 }
69
70 // Whether player is mesmerised by the given monster.
beheld_by(const monster & mon) const71 bool player::beheld_by(const monster &mon) const
72 {
73 return find(begin(beholders), end(beholders), mon.mid) != end(beholders);
74 }
75
76 // Checks whether a beholder keeps you from moving to
77 // target, and returns one if it exists.
get_beholder(const coord_def & target) const78 monster* player::get_beholder(const coord_def &target) const
79 {
80 for (mid_t beh : beholders)
81 {
82 monster* mon = monster_by_mid(beh);
83 // The monster may have died.
84 if (!mon)
85 continue;
86 const int olddist = grid_distance(pos(), mon->pos());
87 const int newdist = grid_distance(target, mon->pos());
88
89 if (olddist < newdist)
90 return mon;
91 }
92 return nullptr;
93 }
94
get_any_beholder() const95 monster* player::get_any_beholder() const
96 {
97 if (!beholders.empty())
98 return monster_by_mid(beholders[0]);
99 else
100 return nullptr;
101 }
102
103 // Removes a monster from the list of beholders if present.
remove_beholder(const monster & mon)104 void player::remove_beholder(const monster& mon)
105 {
106 for (unsigned int i = 0; i < beholders.size(); i++)
107 if (beholders[i] == mon.mid)
108 {
109 beholders.erase(beholders.begin() + i);
110 _removed_beholder();
111 return;
112 }
113 }
114
115 // Clear the list of beholders. Doesn't message.
clear_beholders()116 void player::clear_beholders()
117 {
118 beholders.clear();
119 duration[DUR_MESMERISED] = 0;
120 you.duration[DUR_MESMERISE_IMMUNE] = random_range(21, 40);
121 }
122
_removed_beholder_msg(const monster * mons)123 static void _removed_beholder_msg(const monster *mons)
124 {
125 if (!mons)
126 return;
127
128 const monster &mon = *mons;
129 if (!mon.alive() || !mons_is_siren_beholder(mon)
130 || mon.submerged() || !you.see_cell(mon.pos()))
131 {
132 return;
133 }
134
135 if (is_sanctuary(you.pos()) && !mons_is_fleeing(mon))
136 {
137 if (mons_is_siren_beholder(mon))
138 {
139 if (you.can_see(mon))
140 {
141 mprf("%s's singing becomes strangely muted.",
142 mon.name(DESC_THE).c_str());
143 }
144 else
145 mpr("Something's singing becomes strangely muted.");
146 }
147 else
148 {
149 if (you.can_see(mon))
150 mprf("%s's is no longer quite as mesmerising!", mon.name(DESC_THE).c_str());
151 else
152 mpr("Your mesmeriser suddenly seems less interesting!");
153 }
154
155 return;
156 }
157
158 if (you.can_see(mon))
159 {
160 if (silenced(you.pos()) || silenced(mon.pos()))
161 {
162 if (mons_is_siren_beholder(mon))
163 {
164 mprf("You can no longer hear %s's singing!",
165 mon.name(DESC_THE).c_str());
166 }
167 else
168 mpr("The silence clears your mind.");
169 return;
170 }
171
172 if (mons_is_siren_beholder(mon))
173 mprf("%s stops singing.", mon.name(DESC_THE).c_str());
174 else
175 mprf("%s is no longer quite as mesmerising!", mon.name(DESC_THE).c_str());
176
177 return;
178 }
179
180 if (mons_is_siren_beholder(mon))
181 mpr("Something stops singing.");
182 else
183 mpr("Your mesmeriser is now quite boring!");
184 }
185
186 // Update all beholders' status after changes.
update_beholders()187 void player::update_beholders()
188 {
189 if (!beheld())
190 return;
191 bool removed = false;
192 for (int i = beholders.size() - 1; i >= 0; i--)
193 {
194 const monster* mon = monster_by_mid(beholders[i]);
195 if (!possible_beholder(mon))
196 {
197 beholders.erase(beholders.begin() + i);
198 removed = true;
199
200 // If that was the last one, clear the duration before
201 // printing any subsequent messages, or a --more-- can
202 // crash (#6547).
203 _removed_beholder(true);
204 _removed_beholder_msg(mon);
205 }
206 }
207 if (removed)
208 _removed_beholder();
209 }
210
211 // Update a single beholder.
update_beholder(const monster * mon)212 void player::update_beholder(const monster* mon)
213 {
214 if (possible_beholder(mon))
215 return;
216 for (unsigned int i = 0; i < beholders.size(); i++)
217 if (beholders[i] == mon->mid)
218 {
219 beholders.erase(beholders.begin() + i);
220 // Do this dance to clear the duration before printing messages
221 // (#8844), but still print all messages in the right order.
222 _removed_beholder(true);
223 _removed_beholder_msg(mon);
224 _removed_beholder();
225 return;
226 }
227 }
228
229 // Helper function that resets the duration and messages if the player
230 // is no longer mesmerised.
_removed_beholder(bool quiet)231 void player::_removed_beholder(bool quiet)
232 {
233 if (beholders.empty())
234 {
235 duration[DUR_MESMERISED] = 0;
236 you.duration[DUR_MESMERISE_IMMUNE] = random_range(21, 40);
237 if (!quiet)
238 {
239 mprf(MSGCH_DURATION,
240 coinflip() ? "You break out of your daze!"
241 : "You are no longer entranced.");
242 }
243 }
244 }
245
246 // Helper function that checks whether the given monster is a possible
247 // beholder.
possible_beholder(const monster * mon) const248 bool player::possible_beholder(const monster* mon) const
249 {
250 if (crawl_state.game_is_arena())
251 return false;
252
253 return mon && mon->alive() && !mon->submerged()
254 && cell_see_cell(pos(), mon->pos(), LOS_SOLID_SEE) && mon->see_cell_no_trans(pos())
255 && !mon->wont_attack() && !mon->pacified()
256 && ((mons_is_siren_beholder(mon->type)
257 || mon->has_spell(SPELL_MESMERISE))
258 && !silenced(pos())
259 && !mon->is_silenced()
260 && !mon->confused()
261 && !mon->asleep() && !mon->cannot_move()
262 && !mon->berserk_or_insane()
263 && !mons_is_fleeing(*mon)
264 && !is_sanctuary(pos())
265 || player_equip_unrand(UNRAND_DEMON_AXE));
266 }
267