Formatting fixes.
[crawl:rriegs-crawl.git] / crawl-ref / source / spl-other.cc
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 #include "externs.h"
11
12 #include "coord.h"
13 #include "delay.h"
14 #include "env.h"
15 #include "food.h"
16 #include "godcompanions.h"
17 #include "godconduct.h"
18 #include "itemname.h"
19 #include "itemprop.h"
20 #include "items.h"
21 #include "libutil.h"
22 #include "makeitem.h"
23 #include "message.h"
24 #include "misc.h"
25 #include "mon-iter.h"
26 #include "mon-place.h"
27 #include "mon-util.h"
28 #include "place.h"
29 #include "player.h"
30 #include "player-stats.h"
31 #include "potion.h"
32 #include "religion.h"
33 #include "spl-util.h"
34 #include "stuff.h"
35 #include "terrain.h"
36 #include "transform.h"
37
38 spret_type cast_cure_poison(int pow, bool fail)
39 {
40     if (!you.duration[DUR_POISONING])
41     {
42         canned_msg(MSG_NOTHING_HAPPENS);
43         return SPRET_ABORT;
44     }
45
46     fail_check();
47     reduce_poison_player(2 + random2(pow) + random2(3));
48     return SPRET_SUCCESS;
49 }
50
51 spret_type cast_sublimation_of_blood(int pow, bool fail)
52 {
53     bool success = false;
54
55     int wielded = you.equip[EQ_WEAPON];
56
57     if (wielded != -1)
58     {
59         if (you.inv[wielded].base_type == OBJ_FOOD
60             && you.inv[wielded].sub_type == FOOD_CHUNK)
61         {
62             fail_check();
63             success = true;
64
65             mpr("The chunk of flesh you are holding crumbles to dust.");
66
67             mpr("A flood of magical energy pours into your mind!");
68
69             inc_mp(5 + random2(2 + pow / 15));
70
71             dec_inv_item_quantity(wielded, 1);
72
73             if (mons_genus(you.inv[wielded].mon_type) == MONS_ORC)
74                 did_god_conduct(DID_DESECRATE_ORCISH_REMAINS, 2);
75             if (mons_class_holiness(you.inv[wielded].mon_type) == MH_HOLY)
76                 did_god_conduct(DID_DESECRATE_HOLY_REMAINS, 2);
77         }
78         else if (is_blood_potion(you.inv[wielded])
79                  && item_type_known(you.inv[wielded]))
80         {
81             fail_check();
82             success = true;
83
84             mprf("The blood within %s froths and boils.",
85                  you.inv[wielded].quantity > 1 ? "one of your flasks"
86                                                : "the flask you are holding");
87
88             mpr("A flood of magical energy pours into your mind!");
89
90             inc_mp(5 + random2(2 + pow / 15));
91
92             remove_oldest_blood_potion(you.inv[wielded]);
93             dec_inv_item_quantity(wielded, 1);
94         }
95         else
96             wielded = -1;
97     }
98
99     if (wielded == -1)
100     {
101         if (you.duration[DUR_DEATHS_DOOR])
102         {
103             mpr("A conflicting enchantment prevents the spell from "
104                 "coming into effect.");
105         }
106         else if (!you.can_bleed(false))
107         {
108             mpr("You don't have enough blood to draw power from your "
109                 "own body.");
110         }
111         else if (!enough_hp(2, true))
112             mpr("Your attempt to draw power from your own body fails.");
113         else
114         {
115             int food = 0;
116
117             while (you.magic_points < you.max_magic_points && you.hp > 1
118                    && (you.is_undead != US_SEMI_UNDEAD
119                        || you.hunger - food >= 7000))
120             {
121                 fail_check();
122                 success = true;
123
124                 inc_mp(1);
125                 dec_hp(1, false);
126
127                 if (you.is_undead == US_SEMI_UNDEAD)
128                     food += 15;
129
130                 for (int loopy = 0; loopy < (you.hp > 1 ? 3 : 0); ++loopy)
131                     if (x_chance_in_y(6, pow))
132                         dec_hp(1, false);
133
134                 if (x_chance_in_y(6, pow))
135                     break;
136             }
137             if (success)
138                 mpr("You draw magical energy from your own body!");
139             else
140                 mpr("Your attempt to draw power from your own body fails.");
141
142             make_hungry(food, false);
143         }
144     }
145
146     return (success ? SPRET_SUCCESS : SPRET_ABORT);
147 }
148
149 spret_type cast_death_channel(int pow, god_type god, bool fail)
150 {
151     if (you.duration[DUR_DEATH_CHANNEL] >= 30 * BASELINE_DELAY)
152     {
153         canned_msg(MSG_NOTHING_HAPPENS);
154         return SPRET_ABORT;
155     }
156
157     fail_check();
158     mpr("Malign forces permeate your being, awaiting release.");
159
160     you.increase_duration(DUR_DEATH_CHANNEL, 15 + random2(1 + pow/3), 100);
161
162     if (god != GOD_NO_GOD)
163         you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = static_cast<int>(god);
164
165     return SPRET_SUCCESS;
166 }
167
168 spret_type cast_recall(bool fail)
169 {
170     fail_check();
171     start_recall(0);
172     return SPRET_SUCCESS;
173 }
174
175 struct recall_sorter
176 {
177     bool operator()(const pair<mid_t,int> &a, const pair<mid_t,int> &b)
178     {
179         return a.second > b.second;
180     }
181 };
182
183 // Type recalled:
184 // 0 = anything
185 // 1 = undead only (Yred religion ability)
186 // 2 = orcs only (Beogh religion ability)
187 void start_recall(int type)
188 {
189     // Assemble the recall list.
190     vector<pair<mid_t, int> > rlist;
191
192     you.recall_list.clear();
193     for (monster_iterator mi; mi; ++mi)
194     {
195         if (mi->type == MONS_NO_MONSTER)
196             continue;
197
198         if (!mi->friendly())
199             continue;
200
201         if (mons_class_is_stationary(mi->type)
202             || mons_is_conjured(mi->type))
203         {
204             continue;
205         }
206
207         if (!monster_habitable_grid(*mi, DNGN_FLOOR))
208             continue;
209
210         if (type == 1) // undead
211         {
212             if (mi->holiness() != MH_UNDEAD)
213                 continue;
214         }
215         else if (type == 2) // Beogh
216         {
217             if (!is_orcish_follower(*mi))
218                 continue;
219         }
220
221         pair<mid_t, int> m = make_pair(mi->mid, mi->hit_dice);
222         rlist.push_back(m);
223     }
224
225     if (type > 0 && branch_allows_followers(you.where_are_you))
226         populate_offlevel_recall_list(rlist);
227
228     if (rlist.size() > 0)
229     {
230         // Sort the recall list roughly by HD, randomizing a little
231         for (unsigned int i = 0; i < rlist.size(); ++i)
232             rlist[i].second += random2(10);
233         sort(rlist.begin(), rlist.end(), recall_sorter());
234
235         you.recall_list.clear();
236         for (unsigned int i = 0; i < rlist.size(); ++i)
237             you.recall_list.push_back(rlist[i].first);
238
239         you.attribute[ATTR_NEXT_RECALL_INDEX] = 1;
240         you.attribute[ATTR_NEXT_RECALL_TIME] = 0;
241         mpr("You begin recalling your allies.");
242     }
243     else
244         mpr("Nothing appears to have answered your call.");
245 }
246
247 // Attempt to recall a single monster by mid, which might be either on or off
248 // our current level. Returns whether this monster was successfully recalled.
249 static bool _try_recall(mid_t mid)
250 {
251     monster* mons = monster_by_mid(mid);
252     // Either it's dead or off-level.
253     if (!mons)
254         return recall_offlevel_ally(mid);
255     else if (mons->alive())
256     {
257         // Don't recall monsters that are currently in sight.
258         if (mons->see_cell_no_trans(you.pos()))
259             return false;
260         else
261         {
262             coord_def empty;
263             if (empty_surrounds(you.pos(), DNGN_FLOOR, 3, false, empty)
264                 && mons->move_to_pos(empty))
265             {
266                 simple_monster_message(mons, " is recalled.");
267                 return true;
268             }
269         }
270     }
271
272     return false;
273 }
274
275 // Attempt to recall a number of allies proportional to how much time
276 // has passed. Once the list has been fully processed, terminate the
277 // status.
278 void do_recall(int time)
279 {
280     while (time > you.attribute[ATTR_NEXT_RECALL_TIME])
281     {
282         // Try to recall an ally.
283         mid_t mid = you.recall_list[you.attribute[ATTR_NEXT_RECALL_INDEX]-1];
284         you.attribute[ATTR_NEXT_RECALL_INDEX]++;
285         if (_try_recall(mid))
286         {
287             time -= you.attribute[ATTR_NEXT_RECALL_TIME];
288             you.attribute[ATTR_NEXT_RECALL_TIME] = 3 + random2(4);
289         }
290         if ((unsigned int)you.attribute[ATTR_NEXT_RECALL_INDEX] >
291              you.recall_list.size())
292         {
293             end_recall();
294             mpr("You finish recalling your allies.");
295             return;
296         }
297     }
298
299     you.attribute[ATTR_NEXT_RECALL_TIME] -= time;
300     return;
301 }
302
303 void end_recall()
304 {
305     you.attribute[ATTR_NEXT_RECALL_INDEX] = 0;
306     you.attribute[ATTR_NEXT_RECALL_TIME] = 0;
307     you.recall_list.clear();
308 }
309
310 // Cast_phase_shift: raises evasion (by 8 currently) via Translocations.
311 spret_type cast_phase_shift(int pow, bool fail)
312 {
313     fail_check();
314     if (!you.duration[DUR_PHASE_SHIFT])
315         mpr("You feel the strange sensation of being on two planes at once.");
316     else
317         mpr("You feel the material plane grow further away.");
318
319     you.increase_duration(DUR_PHASE_SHIFT, 5 + random2(pow), 30);
320     you.redraw_evasion = true;
321     return SPRET_SUCCESS;
322 }
323
324 static bool _feat_is_passwallable(dungeon_feature_type feat)
325 {
326     // Worked stone walls are out, they're not diggable and
327     // are used for impassable walls...
328     switch (feat)
329     {
330     case DNGN_ROCK_WALL:
331     case DNGN_SLIMY_WALL:
332     case DNGN_CLEAR_ROCK_WALL:
333         return true;
334     default:
335         return false;
336     }
337 }
338
339 spret_type cast_passwall(const coord_def& delta, int pow, bool fail)
340 {
341     int shallow = 1 + you.skill(SK_EARTH_MAGIC) / 8;
342     int range = shallow + random2(pow) / 25;
343     int maxrange = shallow + pow / 25;
344
345     coord_def dest;
346     for (dest = you.pos() + delta;
347          in_bounds(dest) && _feat_is_passwallable(grd(dest));
348          dest += delta)
349     {}
350
351     int walls = (dest - you.pos()).rdist() - 1;
352     if (walls == 0)
353     {
354         mpr("That's not a passable wall.");
355         return SPRET_ABORT;
356     }
357
358     fail_check();
359
360     // Below here, failing to cast yields information to the
361     // player, so we don't make the spell abort (return true).
362     if (!in_bounds(dest))
363         mpr("You sense an overwhelming volume of rock.");
364     else if (feat_is_solid(grd(dest)))
365         mpr("Something is blocking your path through the rock.");
366     else if (walls > maxrange)
367         mpr("This rock feels extremely deep.");
368     else if (walls > range)
369         mpr("You fail to penetrate the rock.");
370     else
371     {
372         string msg;
373         if (grd(dest) == DNGN_DEEP_WATER)
374             msg = "You sense a large body of water on the other side of the rock.";
375         else if (grd(dest) == DNGN_LAVA)
376             msg = "You sense an intense heat on the other side of the rock.";
377
378         if (check_moveto(dest, "passwall", msg))
379         {
380             // Passwall delay is reduced, and the delay cannot be interrupted.
381             start_delay(DELAY_PASSWALL, 1 + walls, dest.x, dest.y);
382         }
383     }
384     return SPRET_SUCCESS;
385 }
386
387 static int _intoxicate_monsters(coord_def where, int pow, int, actor *)
388 {
389     monster* mons = monster_at(where);
390     if (mons == NULL
391         || mons_intel(mons) < I_NORMAL
392         || mons->holiness() != MH_NATURAL
393         || mons->res_poison() > 0)
394     {
395         return 0;
396     }
397
398     if (x_chance_in_y(40 + pow/3, 100))
399     {
400         if (mons->check_clarity(false))
401             return 1;
402         mons->add_ench(mon_enchant(ENCH_CONFUSION, 0, &you));
403         simple_monster_message(mons, " looks rather confused.");
404         return 1;
405     }
406     return 0;
407 }
408
409 spret_type cast_intoxicate(int pow, bool fail)
410 {
411     fail_check();
412     mpr("You radiate an intoxicating aura.");
413     if (x_chance_in_y(60 - pow/3, 100))
414         potion_effect(POT_CONFUSION, 10 + (100 - pow) / 10);
415
416     if (one_chance_in(20)
417         && lose_stat(STAT_INT, 1 + random2(3), false,
418                       "casting intoxication"))
419     {
420         mpr("Your head spins!");
421     }
422
423     apply_area_visible(_intoxicate_monsters, pow);
424     return SPRET_SUCCESS;
425 }
426
427 void remove_condensation_shield()
428 {
429     mpr("Your icy shield evaporates.", MSGCH_DURATION);
430     you.duration[DUR_CONDENSATION_SHIELD] = 0;
431     you.redraw_armour_class = true;
432 }
433
434 spret_type cast_condensation_shield(int pow, bool fail)
435 {
436     if (you.shield() || you.duration[DUR_FIRE_SHIELD])
437     {
438         canned_msg(MSG_SPELL_FIZZLES);
439         return SPRET_ABORT;
440     }
441
442     fail_check();
443
444     if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
445         mpr("The disc of vapour around you crackles some more.");
446     else
447         mpr("A crackling disc of dense vapour forms in the air!");
448     you.increase_duration(DUR_CONDENSATION_SHIELD, 15 + random2(pow), 40);
449     you.redraw_armour_class = true;
450
451     return SPRET_SUCCESS;
452 }
453
454 spret_type cast_stoneskin(int pow, bool fail)
455 {
456     if (you.form != TRAN_NONE
457         && you.form != TRAN_APPENDAGE
458         && you.form != TRAN_STATUE
459         && you.form != TRAN_BLADE_HANDS)
460     {
461         mpr("This spell does not affect your current form.");
462         return SPRET_ABORT;
463     }
464
465     if (you.duration[DUR_ICY_ARMOUR])
466     {
467         mpr("This spell conflicts with another spell still in effect.");
468         return SPRET_ABORT;
469     }
470
471     fail_check();
472
473     if (you.duration[DUR_STONESKIN])
474         mpr("Your skin feels harder.");
475     else
476     {
477         if (you.form == TRAN_STATUE)
478             mpr("Your stone body feels more resilient.");
479         else
480             mpr("Your skin hardens.");
481
482         you.redraw_armour_class = true;
483     }
484
485     you.increase_duration(DUR_STONESKIN, 10 + random2(pow) + random2(pow), 50);
486
487     return SPRET_SUCCESS;
488 }
489
490 spret_type cast_darkness(int pow, bool fail)
491 {
492     if (you.haloed())
493     {
494         mpr("It would have no effect in that bright light!");
495         return SPRET_ABORT;
496     }
497
498     fail_check();
499     if (you.duration[DUR_DARKNESS])
500         mprf(MSGCH_DURATION, "It gets a bit darker.");
501     else
502         mprf(MSGCH_DURATION, "It gets dark.");
503     you.increase_duration(DUR_DARKNESS, 15 + random2(1 + pow/3), 100);
504     update_vision_range();
505
506     return SPRET_SUCCESS;
507 }