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