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