1 /**
2 * @file
3 * @brief Tracking permallies granted by Yred and Beogh.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "god-companions.h"
9
10 #include <algorithm>
11
12 #include "branch.h"
13 #include "dgn-overview.h"
14 #include "message.h"
15 #include "mon-death.h"
16 #include "mon-util.h"
17 #include "religion.h"
18 #include "spl-other.h"
19 #include "tag-version.h"
20
21 map<mid_t, companion> companion_list;
22
companion(const monster & m)23 companion::companion(const monster& m)
24 : mons(follower(m)), level(level_id::current()), timestamp(you.elapsed_time)
25 {
26 }
27
init_companions()28 void init_companions()
29 {
30 companion_list.clear();
31 }
32
add_companion(monster * mons)33 void add_companion(monster* mons)
34 {
35 ASSERT(mons->alive());
36 // Right now this is a special case for Saint Roka, but
37 // future orcish uniques should behave in the same way.
38 mons->props["no_annotate"] = true;
39 remove_unique_annotation(mons);
40 companion_list[mons->mid] = companion(*mons);
41 }
42
remove_companion(monster * mons)43 void remove_companion(monster* mons)
44 {
45 mons->props["no_annotate"] = false;
46 set_unique_annotation(mons);
47 companion_list.erase(mons->mid);
48 }
49
remove_enslaved_soul_companion()50 void remove_enslaved_soul_companion()
51 {
52 for (auto &entry : companion_list)
53 {
54 monster* mons = monster_by_mid(entry.first);
55 if (!mons)
56 mons = &entry.second.mons.mons;
57 if (mons_enslaved_soul(*mons))
58 {
59 remove_companion(mons);
60 return;
61 }
62 }
63 }
64
remove_all_companions(god_type god)65 void remove_all_companions(god_type god)
66 {
67 for (auto i = companion_list.begin(); i != companion_list.end();)
68 {
69 monster* mons = monster_by_mid(i->first);
70 if (!mons)
71 mons = &i->second.mons.mons;
72 if (mons_is_god_gift(*mons, god))
73 companion_list.erase(i++);
74 else
75 ++i;
76 }
77 }
78
move_companion_to(const monster * mons,const level_id lid)79 void move_companion_to(const monster* mons, const level_id lid)
80 {
81 // If it's taking stairs, that means the player is heading ahead of it,
82 // so we shouldn't relocate the monster until it actually arrives
83 // (or we can clone things on the other end)
84 if (!(mons->flags & MF_TAKING_STAIRS))
85 {
86 companion_list[mons->mid].level = lid;
87 companion_list[mons->mid].mons = follower(*mons);
88 companion_list[mons->mid].timestamp = you.elapsed_time;
89 }
90 }
91
update_companions()92 void update_companions()
93 {
94 for (auto &entry : companion_list)
95 {
96 monster* mons = monster_by_mid(entry.first);
97 if (mons)
98 {
99 if (mons->is_divine_companion())
100 {
101 ASSERT(mons->alive());
102 entry.second.mons = follower(*mons);
103 entry.second.timestamp = you.elapsed_time;
104 }
105 }
106 }
107 }
108
populate_offlevel_recall_list(vector<pair<mid_t,int>> & recall_list)109 void populate_offlevel_recall_list(vector<pair<mid_t, int> > &recall_list)
110 {
111 for (auto &entry : companion_list)
112 {
113 int mid = entry.first;
114 companion &comp = entry.second;
115 if (companion_is_elsewhere(mid, true))
116 {
117 // Recall can't pull monsters out of the Abyss
118 if (comp.level.branch == BRANCH_ABYSS)
119 continue;
120
121 recall_list.emplace_back(mid, comp.mons.mons.get_experience_level());
122 }
123 }
124 }
125
126 /**
127 * Attempt to recall an ally from offlevel.
128 *
129 * @param mid The ID of the monster to be recalled.
130 * @return Whether the monster was successfully recalled onto the level.
131 * Note that the monster may not still be alive or onlevel, due to shafts, etc,
132 * but they were here at least briefly!
133 */
recall_offlevel_ally(mid_t mid)134 bool recall_offlevel_ally(mid_t mid)
135 {
136 if (!companion_is_elsewhere(mid, true))
137 return false;
138
139 companion* comp = &companion_list[mid];
140 monster* mons = comp->mons.place(true);
141 if (!mons)
142 return false;
143
144 // The monster is now on this level
145 remove_monster_from_transit(comp->level, mid);
146 comp->level = level_id::current();
147 simple_monster_message(*mons, " is recalled.");
148
149 // Now that the monster is onlevel, we can safely apply traps to it.
150 // old location isn't very meaningful, so use current loc
151 mons->apply_location_effects(mons->pos());
152 // check if it was killed/shafted by a trap...
153 if (!mons->alive())
154 return true; // still successfully recalled!
155
156 // Catch up time for off-level monsters
157 // (Suppress messages so that we don't get expiry messages for things that
158 // supposedly wore off ages ago)
159 {
160 msg::suppress msg;
161
162 int turns = you.elapsed_time - comp->timestamp;
163 // Note: these are auts, not turns, thus healing is 10 times as fast as
164 // for other monsters, confusion goes away after a single turn, etc.
165
166 mons->heal(div_rand_round(turns * mons->off_level_regen_rate(), 100));
167
168 if (turns >= 10 && mons->alive())
169 {
170 // Remove confusion manually (so that the monster
171 // doesn't blink after being recalled)
172 mons->del_ench(ENCH_CONFUSION, true);
173 mons->timeout_enchantments(turns / 10);
174 }
175 }
176 recall_orders(mons);
177
178 return true;
179 }
180
companion_is_elsewhere(mid_t mid,bool must_exist)181 bool companion_is_elsewhere(mid_t mid, bool must_exist)
182 {
183 if (companion_list.count(mid))
184 {
185 return companion_list[mid].level != level_id::current()
186 || (player_in_branch(BRANCH_PANDEMONIUM)
187 && companion_list[mid].level.branch == BRANCH_PANDEMONIUM
188 && !monster_by_mid(mid));
189 }
190
191 return !must_exist;
192 }
193
wizard_list_companions()194 void wizard_list_companions()
195 {
196 if (companion_list.size() == 0)
197 {
198 mpr("You have no companions.");
199 return;
200 }
201
202 for (auto &entry : companion_list)
203 {
204 companion &comp = entry.second;
205 monster &mon = comp.mons.mons;
206 mprf("%s (%d)(%s:%d)", mon.name(DESC_PLAIN, true).c_str(), mon.mid,
207 branches[comp.level.branch].abbrevname, comp.level.depth);
208 }
209 }
210
211 /**
212 * Returns the mid of the current ancestor granted by Hepliaklqana, if any. If none
213 * exists, returns MID_NOBODY.
214 *
215 * The ancestor is *not* guaranteed to be on-level, even if it exists; check
216 * the companion_list before doing anything rash!
217 *
218 * @return The mid_t of the player's ancestor, or MID_NOBODY if none exists.
219 */
hepliaklqana_ancestor()220 mid_t hepliaklqana_ancestor()
221 {
222 for (auto &entry : companion_list)
223 if (mons_is_hepliaklqana_ancestor(entry.second.mons.mons.type))
224 return entry.first;
225 return MID_NOBODY;
226 }
227
228 /**
229 * Returns the a pointer to the current ancestor granted by Hepliaklqana, if
230 * any. If none exists, returns null.
231 *
232 * The ancestor is *not* guaranteed to be on-level, even if it exists; check
233 * the companion_list before doing anything rash!
234 *
235 * @return The player's ancestor, or nullptr if none exists.
236 */
hepliaklqana_ancestor_mon()237 monster* hepliaklqana_ancestor_mon()
238 {
239 const mid_t ancestor_mid = hepliaklqana_ancestor();
240 if (ancestor_mid == MID_NOBODY)
241 return nullptr;
242
243 monster* ancestor = monster_by_mid(ancestor_mid);
244 if (ancestor)
245 return ancestor;
246
247 for (auto &entry : companion_list)
248 if (mons_is_hepliaklqana_ancestor(entry.second.mons.mons.type))
249 return &entry.second.mons.mons;
250 // should never reach this...
251 return nullptr;
252 }
253
254 /**
255 * @return true if the Hepliaklqana ancestor is at full HP and the player can
256 * see this, or if the ancestor is out of sight or does not exist.
257 */
ancestor_full_hp()258 bool ancestor_full_hp()
259 {
260 if (you.religion == GOD_HEPLIAKLQANA)
261 {
262 monster* ancestor = monster_by_mid(hepliaklqana_ancestor());
263 if (ancestor == nullptr)
264 return true;
265 return !you.can_see(*ancestor)
266 || ancestor->hit_points == ancestor->max_hit_points;
267 }
268 return true;
269 }
270
271 #if TAG_MAJOR_VERSION == 34
272 // A temporary routine to clean up some references to invalid companions and
273 // prevent crashes on load. Should be unnecessary once the cloning bugs that
274 // allow the creation of these invalid companions are fully mopped up
fixup_bad_companions()275 void fixup_bad_companions()
276 {
277 for (auto i = companion_list.begin(); i != companion_list.end();)
278 {
279 if (invalid_monster_type(i->second.mons.mons.type))
280 companion_list.erase(i++);
281 else
282 ++i;
283 }
284 }
285
maybe_bad_priest_monster(monster & mons)286 bool maybe_bad_priest_monster(monster &mons)
287 {
288 // prior to e6d7efa92cb0, if a follower got polymorphed to a form that
289 // satisfied is_priest, its god got cleared. This resulted in Beogh
290 // followers potentially getting cloned on level load, resulting in
291 // duplicate mids or a corrupted mid cache depending on ordering. This is
292 // now fixed up in tag_read_level_load.
293 return mons.alive() && mons.attitude == ATT_FRIENDLY
294 && mons.god == GOD_NAMELESS;
295 }
296
fixup_bad_priest_monster(monster & mons)297 void fixup_bad_priest_monster(monster &mons)
298 {
299 if (!maybe_bad_priest_monster(mons))
300 return;
301 mprf(MSGCH_ERROR, "Removing corrupted ex-follower from level: %s.",
302 mons.full_name(DESC_PLAIN).c_str());
303 monster_die(mons, KILL_RESET, -1, true, false);
304 }
305 #endif
306