1 /**
2  * @file
3  * @brief Non-enchantment spells that didn't fit anywhere else.
4  *           Mostly Transmutations.
5 **/
6 
7 #include "AppHdr.h"
8 
9 #include "spl-other.h"
10 
11 #include "act-iter.h"
12 #include "coordit.h"
13 #include "delay.h"
14 #include "env.h"
15 #include "god-companions.h"
16 #include "libutil.h"
17 #include "message.h"
18 #include "mon-place.h"
19 #include "mon-util.h"
20 #include "movement.h" // passwall
21 #include "place.h"
22 #include "potion.h"
23 #include "religion.h"
24 #include "spl-util.h"
25 #include "terrain.h"
26 
cast_sublimation_of_blood(int pow,bool fail)27 spret cast_sublimation_of_blood(int pow, bool fail)
28 {
29     bool success = false;
30 
31     if (you.duration[DUR_DEATHS_DOOR])
32         mpr("You can't draw power from your own body while in death's door.");
33     else if (!you.can_bleed())
34     {
35         if (you.has_mutation(MUT_VAMPIRISM))
36             mpr("You don't have enough blood to draw power from your own body.");
37         else
38             mpr("Your body is bloodless.");
39     }
40     else if (!enough_hp(2, true))
41         mpr("Your attempt to draw power from your own body fails.");
42     else
43     {
44         // Take at most 90% of currhp.
45         const int minhp = max(div_rand_round(you.hp, 10), 1);
46 
47         while (you.magic_points < you.max_magic_points && you.hp > minhp)
48         {
49             fail_check();
50             success = true;
51 
52             inc_mp(1);
53             dec_hp(1, false);
54 
55             for (int i = 0; i < (you.hp > minhp ? 3 : 0); ++i)
56                 if (x_chance_in_y(6, pow))
57                     dec_hp(1, false);
58 
59             if (x_chance_in_y(6, pow))
60                 break;
61         }
62         if (success)
63             mpr("You draw magical energy from your own body!");
64         else
65             mpr("Your attempt to draw power from your own body fails.");
66     }
67 
68     return success ? spret::success : spret::abort;
69 }
70 
cast_death_channel(int pow,god_type god,bool fail)71 spret cast_death_channel(int pow, god_type god, bool fail)
72 {
73     fail_check();
74     mpr("Malign forces permeate your being, awaiting release.");
75 
76     you.increase_duration(DUR_DEATH_CHANNEL, 30 + random2(1 + 2*pow/3), 200);
77 
78     if (god != GOD_NO_GOD)
79         you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = static_cast<int>(god);
80 
81     return spret::success;
82 }
83 
start_recall(recall_t type)84 void start_recall(recall_t type)
85 {
86     // Assemble the recall list.
87     typedef pair<mid_t, int> mid_hd;
88     vector<mid_hd> rlist;
89 
90     you.recall_list.clear();
91     for (monster_iterator mi; mi; ++mi)
92     {
93         if (!mons_is_recallable(&you, **mi))
94             continue;
95 
96         if (type == recall_t::yred)
97         {
98             if (!(mi->holiness() & MH_UNDEAD))
99                 continue;
100         }
101         else if (type == recall_t::beogh)
102         {
103             if (!is_orcish_follower(**mi))
104                 continue;
105         }
106 
107         mid_hd m(mi->mid, mi->get_experience_level());
108         rlist.push_back(m);
109     }
110 
111     if (branch_allows_followers(you.where_are_you))
112         populate_offlevel_recall_list(rlist);
113 
114     if (!rlist.empty())
115     {
116         // Sort the recall list roughly
117         for (mid_hd &entry : rlist)
118             entry.second += random2(10);
119         sort(rlist.begin(), rlist.end(), greater_second<mid_hd>());
120 
121         you.recall_list.clear();
122         for (mid_hd &entry : rlist)
123             you.recall_list.push_back(entry.first);
124 
125         you.attribute[ATTR_NEXT_RECALL_INDEX] = 1;
126         you.attribute[ATTR_NEXT_RECALL_TIME] = 0;
127         mpr("You begin recalling your allies.");
128     }
129     else
130         mpr("Nothing appears to have answered your call.");
131 }
132 
133 // Remind a recalled ally (or one skipped due to proximity) not to run
134 // away or wander off.
recall_orders(monster * mons)135 void recall_orders(monster *mons)
136 {
137     // FIXME: is this okay for berserk monsters? We still want them to
138     // stick around...
139 
140     // Don't patrol
141     mons->patrol_point = coord_def(0, 0);
142 
143     // Don't wander
144     mons->behaviour = BEH_SEEK;
145 
146     // Don't pursue distant enemies
147     const actor *foe = mons->get_foe();
148     if (foe && !you.can_see(*foe))
149         mons->foe = MHITYOU;
150 }
151 
152 // Attempt to recall a single monster by mid, which might be either on or off
153 // our current level. Returns whether this monster was successfully recalled.
try_recall(mid_t mid)154 bool try_recall(mid_t mid)
155 {
156     monster* mons = monster_by_mid(mid);
157     // Either it's dead or off-level.
158     if (!mons)
159         return recall_offlevel_ally(mid);
160     else if (mons->alive())
161     {
162         // Don't recall monsters that are already close to the player
163         if (mons->pos().distance_from(you.pos()) < 3
164             && mons->see_cell_no_trans(you.pos()))
165         {
166             recall_orders(mons);
167             return false;
168         }
169         else
170         {
171             coord_def empty;
172             if (find_habitable_spot_near(you.pos(), mons_base_type(*mons), 3, false, empty)
173                 && mons->move_to_pos(empty))
174             {
175                 recall_orders(mons);
176                 simple_monster_message(*mons, " is recalled.");
177                 mons->apply_location_effects(mons->pos());
178                 // mons may have been killed, shafted, etc,
179                 // but they were still recalled!
180                 return true;
181             }
182         }
183     }
184 
185     return false;
186 }
187 
188 // Attempt to recall a number of allies proportional to how much time
189 // has passed. Once the list has been fully processed, terminate the
190 // status.
do_recall(int time)191 void do_recall(int time)
192 {
193     while (time > you.attribute[ATTR_NEXT_RECALL_TIME])
194     {
195         // Try to recall an ally.
196         mid_t mid = you.recall_list[you.attribute[ATTR_NEXT_RECALL_INDEX]-1];
197         you.attribute[ATTR_NEXT_RECALL_INDEX]++;
198         if (try_recall(mid))
199         {
200             time -= you.attribute[ATTR_NEXT_RECALL_TIME];
201             you.attribute[ATTR_NEXT_RECALL_TIME] = 3 + random2(4);
202         }
203         if ((unsigned int)you.attribute[ATTR_NEXT_RECALL_INDEX] >
204              you.recall_list.size())
205         {
206             end_recall();
207             mpr("You finish recalling your allies.");
208             return;
209         }
210     }
211 
212     you.attribute[ATTR_NEXT_RECALL_TIME] -= time;
213     return;
214 }
215 
end_recall()216 void end_recall()
217 {
218     you.attribute[ATTR_NEXT_RECALL_INDEX] = 0;
219     you.attribute[ATTR_NEXT_RECALL_TIME] = 0;
220     you.recall_list.clear();
221 }
222 
_feat_is_passwallable(dungeon_feature_type feat)223 static bool _feat_is_passwallable(dungeon_feature_type feat)
224 {
225     // Worked stone walls are out, they're not diggable and
226     // are used for impassable walls...
227     switch (feat)
228     {
229     case DNGN_ROCK_WALL:
230     case DNGN_SLIMY_WALL:
231     case DNGN_CLEAR_ROCK_WALL:
232         return true;
233     default:
234         return false;
235     }
236 }
237 
passwall_simplified_check(const actor & act)238 bool passwall_simplified_check(const actor &act)
239 {
240     for (adjacent_iterator ai(act.pos(), true); ai; ++ai)
241         if (_feat_is_passwallable(env.grid(*ai)))
242             return true;
243     return false;
244 }
245 
passwall_path(const actor & act,const coord_def & dir,int max_range)246 passwall_path::passwall_path(const actor &act, const coord_def& dir, int max_range)
247     : start(act.pos()), delta(dir.sgn()),
248       range(max_range),
249       dest_found(false)
250 {
251     if (delta.zero())
252         return;
253     ASSERT(range > 0);
254     coord_def pos;
255     // TODO: something better than sgn for delta?
256     for (pos = start + delta;
257          (pos - start).rdist() - 1 <= range;
258          pos += delta)
259     {
260         path.emplace_back(pos);
261         if (in_bounds(pos))
262         {
263             if (!_feat_is_passwallable(env.grid(pos)))
264             {
265                 if (!dest_found)
266                 {
267                     actual_dest = pos;
268                     dest_found = true;
269                 }
270                 if (env.map_knowledge(pos).feat() != DNGN_UNSEEN)
271                     break;
272             }
273         }
274         else if (!dest_found) // no destination in bounds
275         {
276             actual_dest = pos;
277             dest_found = true;
278             break; // can't render oob rays anyways, so no point in considering
279                    // more than one of them
280         }
281     }
282     // if dest_found is false, actual_dest is guaranteed to be out of bounds
283 }
284 
285 /// max walls that there could be, given the player's knowledge and map bounds
max_walls() const286 int passwall_path::max_walls() const
287 {
288     if (path.size() == 0)
289         return 0;
290     // the in_bounds check is in case the player is standing next to bounds
291     return max((int) path.size() - 1, in_bounds(path.back()) ? 0 : 1);
292 }
293 
294 /// actual walls (or max walls, if actual_dest is out of bounds)
actual_walls() const295 int passwall_path::actual_walls() const
296 {
297     return !in_bounds(actual_dest) ?
298             max_walls() :
299             (actual_dest - start).rdist() - 1;
300 }
301 
spell_succeeds() const302 bool passwall_path::spell_succeeds() const
303 {
304     // this isn't really the full story -- since moveto needs to be checked
305     // also.
306     return actual_walls() > 0;
307 }
308 
is_valid(string * fail_msg) const309 bool passwall_path::is_valid(string *fail_msg) const
310 {
311     // does not check moveto cases, incl lava/deep water, since these prompt.
312     if (delta.zero())
313     {
314         if (fail_msg)
315             *fail_msg = "Please select a wall.";
316         return false;
317     }
318     if (actual_walls() == 0)
319     {
320         if (fail_msg)
321             *fail_msg = "There is no adjacent passable wall in that direction.";
322         return false;
323     }
324     if (!dest_found)
325     {
326         if (fail_msg)
327             *fail_msg = "This rock feels extremely deep.";
328         return false;
329     }
330     if (!in_bounds(actual_dest))
331     {
332         if (fail_msg)
333             *fail_msg = "You sense an overwhelming volume of rock.";
334         return false;
335     }
336     const monster *mon = monster_at(actual_dest);
337     if (cell_is_solid(actual_dest) || (mon && mon->is_stationary()))
338     {
339         if (fail_msg)
340             *fail_msg = "Something is blocking your path through the rock.";
341         return false;
342     }
343     return true;
344 }
345 
346 /// find possible destinations, given the player's map knowledge
possible_dests() const347 vector <coord_def> passwall_path::possible_dests() const
348 {
349     // uses comparison to DNGN_UNSEEN so that this works sensibly with magic
350     // mapping etc
351     vector<coord_def> dests;
352     for (auto p : path)
353         if (!in_bounds(p) ||
354             (env.map_knowledge(p).feat() == DNGN_UNSEEN || !cell_is_solid(p)))
355         {
356             dests.push_back(p);
357         }
358     return dests;
359 }
360 
check_moveto() const361 bool passwall_path::check_moveto() const
362 {
363     // assumes is_valid()
364 
365     string terrain_msg;
366     if (env.grid(actual_dest) == DNGN_DEEP_WATER)
367         terrain_msg = "You sense a deep body of water on the other side of the rock.";
368     else if (env.grid(actual_dest) == DNGN_LAVA)
369         terrain_msg = "You sense an intense heat on the other side of the rock.";
370 
371     // Pre-confirm exclusions in unseen squares as well as the actual dest
372     // even if seen, so that this doesn't leak information.
373 
374     // TODO: handle uncertainty in messaging for things other than exclusions
375 
376     return check_moveto_terrain(actual_dest, "passwall", terrain_msg)
377         && check_moveto_exclusions(possible_dests(), "passwall")
378         && check_moveto_cloud(actual_dest, "passwall")
379         && check_moveto_trap(actual_dest, "passwall");
380 
381 }
382 
cast_passwall(const coord_def & c,int pow,bool fail)383 spret cast_passwall(const coord_def& c, int pow, bool fail)
384 {
385     // prompt player to end position-based ice spells
386     if (cancel_harmful_move(false))
387         return spret::abort;
388 
389     coord_def delta = c - you.pos();
390     passwall_path p(you, delta, spell_range(SPELL_PASSWALL, pow));
391     string fail_msg;
392     bool valid = p.is_valid(&fail_msg);
393     if (!p.spell_succeeds())
394     {
395         if (fail_msg.size())
396             mpr(fail_msg);
397         return spret::abort;
398     }
399 
400     fail_check();
401 
402     if (!valid)
403     {
404         if (fail_msg.size())
405             mpr(fail_msg);
406     }
407     else if (p.check_moveto())
408     {
409         start_delay<PasswallDelay>(p.actual_walls() + 1, p.actual_dest);
410         return spret::success;
411     }
412 
413     // at this point, the spell failed or was cancelled. Does it cost MP?
414     vector<coord_def> dests = p.possible_dests();
415     if (dests.size() == 0 ||
416         (dests.size() == 1 && (!in_bounds(dests[0]) ||
417         env.map_knowledge(dests[0]).feat() != DNGN_UNSEEN)))
418     {
419         // if there are no possible destinations, or only 1 that has been seen,
420         // the player already had full knowledge. The !in_bounds case is if they
421         // are standing next to the map edge, which is a leak of sorts, but
422         // already apparent from the targeting (and we leak this info all over
423         // the place, really).
424         return spret::abort;
425     }
426     return spret::success;
427 }
428 
_intoxicate_monsters(coord_def where,int pow,bool tracer)429 static int _intoxicate_monsters(coord_def where, int pow, bool tracer)
430 {
431     monster* mons = monster_at(where);
432     if (mons == nullptr
433         || mons_intel(*mons) < I_HUMAN
434         || !(mons->holiness() & MH_NATURAL)
435         || mons->clarity()
436         || mons->res_poison() >= 3)
437     {
438         return 0;
439     }
440 
441     if (tracer && !you.can_see(*mons))
442         return 0;
443 
444     if (!tracer && monster_resists_this_poison(*mons))
445         return 0;
446 
447     if (!tracer && x_chance_in_y(40 + pow/3, 100))
448     {
449         mons->add_ench(mon_enchant(ENCH_CONFUSION, 0, &you));
450         simple_monster_message(*mons, " looks rather confused.");
451         return 1;
452     }
453     // Just count affectable monsters for the tracer
454     return tracer ? 1 : 0;
455 }
456 
cast_intoxicate(int pow,bool fail,bool tracer)457 spret cast_intoxicate(int pow, bool fail, bool tracer)
458 {
459     if (tracer)
460     {
461         const int work = apply_area_visible([] (coord_def where) {
462             return _intoxicate_monsters(where, 0, true);
463         }, you.pos());
464 
465         return work > 0 ? spret::success : spret::abort;
466     }
467 
468     fail_check();
469     mpr("You attempt to intoxicate your foes!");
470 
471     const int count = apply_area_visible([pow] (coord_def where) {
472         return _intoxicate_monsters(where, pow, false);
473     }, you.pos());
474 
475     if (count > 0)
476     {
477         mprf(MSGCH_WARN, "The world spins around you!");
478         you.increase_duration(DUR_VERTIGO, 4 + count + random2(count + 1));
479         you.redraw_evasion = true;
480     }
481 
482     return spret::success;
483 }
484 
cast_invisibility(int pow,bool fail)485 spret cast_invisibility(int pow, bool fail)
486 {
487     if (!invis_allowed())
488         return spret::abort;
489 
490     fail_check();
491 
492     potionlike_effect(POT_INVISIBILITY, pow);
493     contaminate_player(1000 + random2(1000), true);
494     return spret::success;
495 }
496