Revert "Track who destroys an item; incur Nemelex penance for deck destruction."
[crawl.git] / crawl-ref / source / spl-clouds.cc
1 /**
2  * @file
3  * @brief Cloud creating spells.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "spl-clouds.h"
9 #include "externs.h"
10
11 #include <algorithm>
12
13 #include "beam.h"
14 #include "cloud.h"
15 #include "coord.h"
16 #include "coordit.h"
17 #include "env.h"
18 #include "fprop.h"
19 #include "itemprop.h"
20 #include "items.h"
21 #include "libutil.h"
22 #include "message.h"
23 #include "misc.h"
24 #include "mon-behv.h"
25 #include "mon-util.h"
26 #include "ouch.h"
27 #include "player.h"
28 #include "skills.h"
29 #include "spl-util.h"
30 #include "stuff.h"
31 #include "terrain.h"
32 #include "transform.h"
33 #include "viewchar.h"
34 #include "shout.h"
35
36 static void _burn_tree(coord_def pos)
37 {
38     bolt beam;
39     beam.origin_spell = SPELL_CONJURE_FLAME;
40     beam.range = 1;
41     beam.flavour = BEAM_FIRE;
42     beam.name = "fireball"; // yay doing this by name
43     beam.source = beam.target = pos;
44     beam.set_agent(&you);
45     beam.fire();
46 }
47
48 spret_type conjure_flame(int pow, const coord_def& where, bool fail)
49 {
50     // FIXME: This would be better handled by a flag to enforce max range.
51     if (distance2(where, you.pos()) > dist_range(spell_range(SPELL_CONJURE_FLAME,
52                                                       pow))
53         || !in_bounds(where))
54     {
55         mpr("That's too far away.");
56         return SPRET_ABORT;
57     }
58
59     if (cell_is_solid(where))
60     {
61         switch (grd(where))
62         {
63         case DNGN_METAL_WALL:
64             mpr("You can't ignite solid metal!");
65             break;
66         case DNGN_GREEN_CRYSTAL_WALL:
67             mpr("You can't ignite solid crystal!");
68             break;
69         case DNGN_TREE:
70         case DNGN_MANGROVE:
71             fail_check();
72             _burn_tree(where);
73             return SPRET_SUCCESS;
74         default:
75             mpr("You can't ignite solid rock!");
76             break;
77         }
78         return SPRET_ABORT;
79     }
80
81     const int cloud = env.cgrid(where);
82
83     if (cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_FIRE)
84     {
85         mpr("There's already a cloud there!");
86         return SPRET_ABORT;
87     }
88
89     // Note that self-targetting is handled by SPFLAG_NOT_SELF.
90     monster* mons = monster_at(where);
91     if (mons)
92     {
93         if (you.can_see(mons))
94         {
95             mpr("You can't place the cloud on a creature.");
96             return SPRET_ABORT;
97         }
98
99         fail_check();
100
101         // FIXME: maybe should do _paranoid_option_disable() here?
102         mpr("You see a ghostly outline there, and the spell fizzles.");
103         return SPRET_SUCCESS;      // Don't give free detection!
104     }
105
106     fail_check();
107
108     if (cloud != EMPTY_CLOUD)
109     {
110         // Reinforce the cloud - but not too much.
111         // It must be a fire cloud from a previous test.
112         mpr("The fire roars with new energy!");
113         const int extra_dur = 2 + min(random2(pow) / 2, 20);
114         env.cloud[cloud].decay += extra_dur * 5;
115         env.cloud[cloud].set_whose(KC_YOU);
116     }
117     else
118     {
119         const int durat = min(5 + (random2(pow)/2) + (random2(pow)/2), 23);
120         place_cloud(CLOUD_FIRE, where, durat, &you);
121         mpr("The fire roars!");
122     }
123     noisy(2, where);
124
125     return SPRET_SUCCESS;
126 }
127
128 // Assumes beem.range has already been set. -cao
129 spret_type stinking_cloud(int pow, bolt &beem, bool fail)
130 {
131     beem.name        = "stinking cloud";
132     beem.colour      = GREEN;
133     beem.damage      = dice_def(1, 0);
134     beem.hit         = 20;
135     beem.glyph       = dchar_glyph(DCHAR_FIRED_ZAP);
136     beem.flavour     = BEAM_MEPHITIC;
137     beem.ench_power  = pow;
138     beem.beam_source = MHITYOU;
139     beem.thrower     = KILL_YOU;
140     beem.is_beam     = false;
141     beem.is_explosion = true;
142     beem.aux_source.clear();
143
144     // Fire tracer.
145     beem.source            = you.pos();
146     beem.can_see_invis     = you.can_see_invisible();
147     beem.smart_monster     = true;
148     beem.attitude          = ATT_FRIENDLY;
149     beem.friend_info.count = 0;
150     beem.is_tracer         = true;
151     beem.fire();
152
153     if (beem.beam_cancelled)
154     {
155         // We don't want to fire through friendlies.
156         canned_msg(MSG_OK);
157         return SPRET_ABORT;
158     }
159
160     fail_check();
161
162     // Really fire.
163     beem.is_tracer = false;
164     beem.fire();
165
166     return SPRET_SUCCESS;
167 }
168
169 spret_type cast_big_c(int pow, cloud_type cty, const actor *caster, bolt &beam,
170                       bool fail)
171 {
172     if (distance2(beam.target, you.pos()) > dist_range(beam.range)
173         || !in_bounds(beam.target))
174     {
175         mpr("That is beyond the maximum range.");
176         return SPRET_ABORT;
177     }
178
179     if (cell_is_solid(beam.target))
180     {
181         mpr("You can't place clouds on a wall.");
182         return SPRET_ABORT;
183     }
184
185     //XXX: there should be a better way to specify beam cloud types
186     switch(cty)
187     {
188         case CLOUD_POISON:
189             beam.flavour = BEAM_POISON;
190             beam.name = "blast of poison";
191             break;
192         case CLOUD_HOLY_FLAMES:
193             beam.flavour = BEAM_HOLY;
194             beam.origin_spell = SPELL_HOLY_BREATH;
195             break;
196         case CLOUD_COLD:
197             beam.flavour = BEAM_COLD;
198             beam.name = "freezing blast";
199             break;
200         default:
201             mpr("That kind of cloud doesn't exist!");
202             return SPRET_ABORT;
203     }
204
205     beam.thrower           = KILL_YOU;
206     beam.hit               = AUTOMATIC_HIT;
207     beam.damage            = INSTANT_DEATH; // just a convenient non-zero
208     beam.is_big_cloud      = true;
209     beam.is_tracer         = true;
210     beam.use_target_as_pos = true;
211     beam.affect_endpoint();
212     if (beam.beam_cancelled)
213         return SPRET_ABORT;
214
215     fail_check();
216
217     big_cloud(cty, caster, beam.target, pow, 8 + random2(3), -1);
218     noisy(2, beam.target);
219     return SPRET_SUCCESS;
220 }
221
222 static int _make_a_normal_cloud(coord_def where, int pow, int spread_rate,
223                                 cloud_type ctype, const actor *agent, int colour,
224                                 string name, string tile, int excl_rad)
225 {
226     place_cloud(ctype, where,
227                 (3 + random2(pow / 4) + random2(pow / 4) + random2(pow / 4)),
228                 agent, spread_rate, colour, name, tile, excl_rad);
229
230     return 1;
231 }
232
233 void big_cloud(cloud_type cl_type, const actor *agent,
234                const coord_def& where, int pow, int size, int spread_rate,
235                int colour, string name, string tile)
236 {
237     apply_area_cloud(_make_a_normal_cloud, where, pow, size,
238                      cl_type, agent, spread_rate, colour, name, tile,
239                      -1);
240 }
241
242 spret_type cast_ring_of_flames(int power, bool fail)
243 {
244     // You shouldn't be able to cast this in the rain. {due}
245     if (in_what_cloud(CLOUD_RAIN))
246     {
247         mpr("Your spell sizzles in the rain.");
248         return SPRET_ABORT;
249     }
250
251     fail_check();
252     you.increase_duration(DUR_FIRE_SHIELD,
253                           5 + (power / 10) + (random2(power) / 5), 50,
254                           "The air around you leaps into flame!");
255     manage_fire_shield(1);
256     return SPRET_SUCCESS;
257 }
258
259 void manage_fire_shield(int delay)
260 {
261     ASSERT(you.duration[DUR_FIRE_SHIELD]);
262
263     int old_dur = you.duration[DUR_FIRE_SHIELD];
264
265     you.duration[DUR_FIRE_SHIELD]-= delay;
266     if (you.duration[DUR_FIRE_SHIELD] < 0)
267         you.duration[DUR_FIRE_SHIELD] = 0;
268
269     // Remove fire clouds on top of you
270     if (env.cgrid(you.pos()) != EMPTY_CLOUD
271         && env.cloud[env.cgrid(you.pos())].type == CLOUD_FIRE)
272     {
273         delete_cloud_at(you.pos());
274     }
275
276     if (!you.duration[DUR_FIRE_SHIELD])
277     {
278         mpr("Your ring of flames gutters out.", MSGCH_DURATION);
279         return;
280     }
281
282     int threshold = get_expiration_threshold(DUR_FIRE_SHIELD);
283
284
285     if (old_dur > threshold && you.duration[DUR_FIRE_SHIELD] < threshold)
286         mpr("Your ring of flames is guttering out.", MSGCH_WARN);
287
288     // Place fire clouds all around you
289     for (adjacent_iterator ai(you.pos()); ai; ++ai)
290         if (!feat_is_solid(grd(*ai)) && env.cgrid(*ai) == EMPTY_CLOUD)
291             place_cloud(CLOUD_FIRE, *ai, 1 + random2(6), &you);
292 }
293
294 spret_type cast_corpse_rot(bool fail)
295 {
296     if (!you.res_rotting())
297     {
298         for (stack_iterator si(you.pos()); si; ++si)
299         {
300             if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
301             {
302                 if (!yesno(("Really cast Corpse Rot while standing on " + si->name(DESC_A) + "?").c_str(), false, 'n'))
303                 {
304                     canned_msg(MSG_OK);
305                     return SPRET_ABORT;
306                 }
307                 break;
308             }
309         }
310     }
311     fail_check();
312     corpse_rot(&you);
313     return SPRET_SUCCESS;
314 }
315
316 void corpse_rot(actor* caster)
317 {
318     for (radius_iterator ri(caster->pos(), 6, C_ROUND, caster->is_player() ? you.get_los_no_trans()
319                                                                                     : caster->get_los());
320          ri; ++ri)
321     {
322         if (!is_sanctuary(*ri) && env.cgrid(*ri) == EMPTY_CLOUD)
323             for (stack_iterator si(*ri); si; ++si)
324                 if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
325                 {
326                     // Found a corpse.  Skeletonise it if possible.
327                     if (!mons_skeleton(si->mon_type))
328                     {
329                         item_was_destroyed(*si);
330                         destroy_item(si->index());
331                     }
332                     else
333                         turn_corpse_into_skeleton(*si);
334
335                     place_cloud(CLOUD_MIASMA, *ri, 4+random2avg(16, 3),caster);
336
337                     // Don't look for more corpses here.
338                     break;
339                 }
340     }
341
342     if (you.can_smell() && you.can_see(caster))
343         mpr("You smell decay.");
344
345     // Should make zombies decay into skeletons?
346 }
347
348 int holy_flames(monster* caster, actor* defender)
349 {
350     const coord_def pos = defender->pos();
351     int cloud_count = 0;
352
353     for (adjacent_iterator ai(pos); ai; ++ai)
354     {
355         if (!in_bounds(*ai)
356             || env.cgrid(*ai) != EMPTY_CLOUD
357             || feat_is_solid(grd(*ai))
358             || is_sanctuary(*ai)
359             || monster_at(*ai))
360         {
361             continue;
362         }
363
364         place_cloud(CLOUD_HOLY_FLAMES, *ai, caster->hit_dice * 5, caster);
365
366         cloud_count++;
367     }
368
369     return cloud_count;
370 }