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