Use cell_is_solid where appropriate.
[crawl: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 "act-iter.h"
14 #include "beam.h"
15 #include "cloud.h"
16 #include "coord.h"
17 #include "coordit.h"
18 #include "env.h"
19 #include "fprop.h"
20 #include "itemprop.h"
21 #include "items.h"
22 #include "libutil.h"
23 #include "losglobal.h"
24 #include "message.h"
25 #include "misc.h"
26 #include "mon-behv.h"
27 #include "mon-util.h"
28 #include "ouch.h"
29 #include "player.h"
30 #include "skills.h"
31 #include "spl-util.h"
32 #include "stuff.h"
33 #include "terrain.h"
34 #include "transform.h"
35 #include "viewchar.h"
36 #include "shout.h"
37
38 static void _burn_tree(coord_def pos)
39 {
40     bolt beam;
41     beam.origin_spell = SPELL_CONJURE_FLAME;
42     beam.range = 1;
43     beam.flavour = BEAM_FIRE;
44     beam.name = "fireball"; // yay doing this by name
45     beam.source = beam.target = pos;
46     beam.set_agent(&you);
47     beam.fire();
48 }
49
50 spret_type conjure_flame(int pow, const coord_def& where, bool fail)
51 {
52     // FIXME: This would be better handled by a flag to enforce max range.
53     if (distance2(where, you.pos()) > dist_range(spell_range(SPELL_CONJURE_FLAME,
54                                                       pow))
55         || !in_bounds(where))
56     {
57         mpr("That's too far away.");
58         return SPRET_ABORT;
59     }
60
61     if (cell_is_solid(where))
62     {
63         switch (grd(where))
64         {
65         case DNGN_METAL_WALL:
66             mpr("You can't ignite solid metal!");
67             break;
68         case DNGN_GREEN_CRYSTAL_WALL:
69             mpr("You can't ignite solid crystal!");
70             break;
71         case DNGN_TREE:
72         case DNGN_MANGROVE:
73             fail_check();
74             _burn_tree(where);
75             return SPRET_SUCCESS;
76         default:
77             mpr("You can't ignite solid rock!");
78             break;
79         }
80         return SPRET_ABORT;
81     }
82
83     const int cloud = env.cgrid(where);
84
85     if (cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_FIRE)
86     {
87         mpr("There's already a cloud there!");
88         return SPRET_ABORT;
89     }
90
91     // Note that self-targetting is handled by SPFLAG_NOT_SELF.
92     monster* mons = monster_at(where);
93     if (mons)
94     {
95         if (you.can_see(mons))
96         {
97             mpr("You can't place the cloud on a creature.");
98             return SPRET_ABORT;
99         }
100
101         fail_check();
102
103         // FIXME: maybe should do _paranoid_option_disable() here?
104         mpr("You see a ghostly outline there, and the spell fizzles.");
105         return SPRET_SUCCESS;      // Don't give free detection!
106     }
107
108     fail_check();
109
110     if (cloud != EMPTY_CLOUD)
111     {
112         // Reinforce the cloud - but not too much.
113         // It must be a fire cloud from a previous test.
114         mpr("The fire roars with new energy!");
115         const int extra_dur = 2 + min(random2(pow) / 2, 20);
116         env.cloud[cloud].decay += extra_dur * 5;
117         env.cloud[cloud].set_whose(KC_YOU);
118     }
119     else
120     {
121         const int durat = min(5 + (random2(pow)/2) + (random2(pow)/2), 23);
122         place_cloud(CLOUD_FIRE, where, durat, &you);
123         mpr("The fire roars!");
124     }
125     noisy(2, where);
126
127     return SPRET_SUCCESS;
128 }
129
130 // Assumes beem.range has already been set. -cao
131 spret_type stinking_cloud(int pow, bolt &beem, bool fail)
132 {
133     beem.name        = "stinking cloud";
134     beem.colour      = GREEN;
135     beem.damage      = dice_def(1, 0);
136     beem.hit         = 20;
137     beem.glyph       = dchar_glyph(DCHAR_FIRED_ZAP);
138     beem.flavour     = BEAM_MEPHITIC;
139     beem.ench_power  = pow;
140     beem.beam_source = MHITYOU;
141     beem.thrower     = KILL_YOU;
142     beem.is_beam     = false;
143     beem.is_explosion = true;
144     beem.aux_source.clear();
145
146     // Fire tracer.
147     beem.source            = you.pos();
148     beem.can_see_invis     = you.can_see_invisible();
149     beem.smart_monster     = true;
150     beem.attitude          = ATT_FRIENDLY;
151     beem.friend_info.count = 0;
152     beem.is_tracer         = true;
153     beem.fire();
154
155     if (beem.beam_cancelled)
156     {
157         // We don't want to fire through friendlies.
158         canned_msg(MSG_OK);
159         return SPRET_ABORT;
160     }
161
162     fail_check();
163
164     // Really fire.
165     beem.is_tracer = false;
166     beem.fire();
167
168     return SPRET_SUCCESS;
169 }
170
171 spret_type cast_big_c(int pow, cloud_type cty, const actor *caster, bolt &beam,
172                       bool fail)
173 {
174     if (distance2(beam.target, you.pos()) > dist_range(beam.range)
175         || !in_bounds(beam.target))
176     {
177         mpr("That is beyond the maximum range.");
178         return SPRET_ABORT;
179     }
180
181     if (cell_is_solid(beam.target))
182     {
183         mpr("You can't place clouds on a wall.");
184         return SPRET_ABORT;
185     }
186
187     //XXX: there should be a better way to specify beam cloud types
188     switch(cty)
189     {
190         case CLOUD_POISON:
191             beam.flavour = BEAM_POISON;
192             beam.name = "blast of poison";
193             break;
194         case CLOUD_HOLY_FLAMES:
195             beam.flavour = BEAM_HOLY;
196             beam.origin_spell = SPELL_HOLY_BREATH;
197             break;
198         case CLOUD_COLD:
199             beam.flavour = BEAM_COLD;
200             beam.name = "freezing blast";
201             break;
202         default:
203             mpr("That kind of cloud doesn't exist!");
204             return SPRET_ABORT;
205     }
206
207     beam.thrower           = KILL_YOU;
208     beam.hit               = AUTOMATIC_HIT;
209     beam.damage            = INSTANT_DEATH; // just a convenient non-zero
210     beam.is_big_cloud      = true;
211     beam.is_tracer         = true;
212     beam.use_target_as_pos = true;
213     beam.affect_endpoint();
214     if (beam.beam_cancelled)
215         return SPRET_ABORT;
216
217     fail_check();
218
219     big_cloud(cty, caster, beam.target, pow, 8 + random2(3), -1);
220     noisy(2, beam.target);
221     return SPRET_SUCCESS;
222 }
223
224 static int _make_a_normal_cloud(coord_def where, int pow, int spread_rate,
225                                 cloud_type ctype, const actor *agent, int colour,
226                                 string name, string tile, int excl_rad)
227 {
228     place_cloud(ctype, where,
229                 (3 + random2(pow / 4) + random2(pow / 4) + random2(pow / 4)),
230                 agent, spread_rate, colour, name, tile, excl_rad);
231
232     return 1;
233 }
234
235 void big_cloud(cloud_type cl_type, const actor *agent,
236                const coord_def& where, int pow, int size, int spread_rate,
237                int colour, string name, string tile)
238 {
239     // The starting point _may_ be a place no cloud can be placed on.
240     apply_area_cloud(_make_a_normal_cloud, where, pow, size,
241                      cl_type, agent, spread_rate, colour, name, tile,
242                      -1);
243 }
244
245 spret_type cast_ring_of_flames(int power, bool fail)
246 {
247     // You shouldn't be able to cast this in the rain. {due}
248     if (in_what_cloud(CLOUD_RAIN))
249     {
250         mpr("Your spell sizzles in the rain.");
251         return SPRET_ABORT;
252     }
253
254     fail_check();
255     you.increase_duration(DUR_FIRE_SHIELD,
256                           5 + (power / 10) + (random2(power) / 5), 50,
257                           "The air around you leaps into flame!");
258     manage_fire_shield(1);
259     return SPRET_SUCCESS;
260 }
261
262 void manage_fire_shield(int delay)
263 {
264     ASSERT(you.duration[DUR_FIRE_SHIELD]);
265
266     int old_dur = you.duration[DUR_FIRE_SHIELD];
267
268     you.duration[DUR_FIRE_SHIELD]-= delay;
269     if (you.duration[DUR_FIRE_SHIELD] < 0)
270         you.duration[DUR_FIRE_SHIELD] = 0;
271
272     // Remove fire clouds on top of you
273     if (env.cgrid(you.pos()) != EMPTY_CLOUD
274         && env.cloud[env.cgrid(you.pos())].type == CLOUD_FIRE)
275     {
276         delete_cloud_at(you.pos());
277     }
278
279     if (!you.duration[DUR_FIRE_SHIELD])
280     {
281         mpr("Your ring of flames gutters out.", MSGCH_DURATION);
282         return;
283     }
284
285     int threshold = get_expiration_threshold(DUR_FIRE_SHIELD);
286
287
288     if (old_dur > threshold && you.duration[DUR_FIRE_SHIELD] < threshold)
289         mpr("Your ring of flames is guttering out.", MSGCH_WARN);
290
291     // Place fire clouds all around you
292     for (adjacent_iterator ai(you.pos()); ai; ++ai)
293         if (!cell_is_solid(*ai) && env.cgrid(*ai) == EMPTY_CLOUD)
294             place_cloud(CLOUD_FIRE, *ai, 1 + random2(6), &you);
295 }
296
297 spret_type cast_corpse_rot(bool fail)
298 {
299     if (!you.res_rotting())
300     {
301         for (stack_iterator si(you.pos()); si; ++si)
302         {
303             if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
304             {
305                 if (!yesno(("Really cast Corpse Rot while standing on " + si->name(DESC_A) + "?").c_str(), false, 'n'))
306                 {
307                     canned_msg(MSG_OK);
308                     return SPRET_ABORT;
309                 }
310                 break;
311             }
312         }
313     }
314     fail_check();
315     corpse_rot(&you);
316     return SPRET_SUCCESS;
317 }
318
319 void corpse_rot(actor* caster)
320 {
321     for (radius_iterator ri(caster->pos(), 6, C_ROUND, caster->is_player() ? you.get_los_no_trans()
322                                                                                     : caster->get_los());
323          ri; ++ri)
324     {
325         if (!is_sanctuary(*ri) && env.cgrid(*ri) == EMPTY_CLOUD)
326             for (stack_iterator si(*ri); si; ++si)
327                 if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
328                 {
329                     // Found a corpse.  Skeletonise it if possible.
330                     if (!mons_skeleton(si->mon_type))
331                     {
332                         item_was_destroyed(*si);
333                         destroy_item(si->index());
334                     }
335                     else
336                         turn_corpse_into_skeleton(*si);
337
338                     place_cloud(CLOUD_MIASMA, *ri, 4+random2avg(16, 3),caster);
339
340                     // Don't look for more corpses here.
341                     break;
342                 }
343     }
344
345     if (you.can_smell() && you.can_see(caster))
346         mpr("You smell decay.");
347
348     // Should make zombies decay into skeletons?
349 }
350
351 int holy_flames(monster* caster, actor* defender)
352 {
353     const coord_def pos = defender->pos();
354     int cloud_count = 0;
355
356     for (adjacent_iterator ai(pos); ai; ++ai)
357     {
358         if (!in_bounds(*ai)
359             || env.cgrid(*ai) != EMPTY_CLOUD
360             || cell_is_solid(*ai)
361             || is_sanctuary(*ai)
362             || monster_at(*ai))
363         {
364             continue;
365         }
366
367         place_cloud(CLOUD_HOLY_FLAMES, *ai, caster->hit_dice * 5, caster);
368
369         cloud_count++;
370     }
371
372     return cloud_count;
373 }
374
375 struct dist2_sorter
376 {
377     coord_def pos;
378     bool operator()(const actor* a, const actor* b)
379     {
380         return distance2(a->pos(), pos) > distance2(b->pos(), pos);
381     }
382 };
383
384 static bool _safe_cloud_spot(const monster* mon, coord_def p)
385 {
386     if (cell_is_solid(p) || env.cgrid(p) != EMPTY_CLOUD)
387         return false;
388
389     if (actor_at(p) && mons_aligned(mon, actor_at(p)))
390         return false;
391
392     return true;
393 }
394
395 void apply_control_winds(const monster* mon)
396 {
397     vector<int> cloud_list;
398     for (distance_iterator di(mon->pos(), true, false, LOS_RADIUS); di; ++di)
399     {
400         if (env.cgrid(*di) != EMPTY_CLOUD
401             && cell_see_cell(mon->pos(), *di, LOS_SOLID)
402             && (di.radius() < 6 || env.cloud[env.cgrid(*di)].type == CLOUD_FOREST_FIRE
403                                 || (actor_at(*di) && mons_aligned(mon, actor_at(*di)))))
404         {
405             cloud_list.push_back(env.cgrid(*di));
406         }
407     }
408
409     bolt wind_beam;
410     wind_beam.hit = AUTOMATIC_HIT;
411     wind_beam.is_beam = true;
412     wind_beam.affects_nothing = true;
413     wind_beam.source = mon->pos();
414     wind_beam.range = LOS_RADIUS;
415     wind_beam.is_tracer = true;
416
417     for (int i = cloud_list.size() - 1; i >= 0; --i)
418     {
419         cloud_struct* cl = &env.cloud[cloud_list[i]];
420         if (cl->type == CLOUD_FOREST_FIRE)
421         {
422             if (you.see_cell(cl->pos))
423                 mpr("The forest fire is smothered by the winds.");
424             delete_cloud(cloud_list[i]);
425             continue;
426         }
427
428         // Leave clouds engulfing hostiles alone
429         if (actor_at(cl->pos) && !mons_aligned(actor_at(cl->pos), mon))
430             continue;
431
432         bool pushed = false;
433
434         coord_def newpos;
435         if (cl->pos != mon->pos())
436         {
437             wind_beam.target = cl->pos;
438             wind_beam.fire();
439             for (unsigned int j = 0; j < wind_beam.path_taken.size() - 1; ++j)
440             {
441                 if (env.cgrid(wind_beam.path_taken[j]) == cloud_list[i])
442                 {
443                     newpos = wind_beam.path_taken[j+1];
444                     if (_safe_cloud_spot(mon, newpos))
445                     {
446                         swap_clouds(newpos, wind_beam.path_taken[j]);
447                         pushed = true;
448                         break;
449                     }
450                 }
451             }
452         }
453
454         if (!pushed)
455         {
456             for (distance_iterator di(cl->pos, true, true, 1); di; ++di)
457             {
458                 if ((newpos.origin() || adjacent(*di, newpos))
459                     && di->distance_from(mon->pos())
460                         == (cl->pos.distance_from(mon->pos()) + 1)
461                     && _safe_cloud_spot(mon, *di))
462                 {
463                     swap_clouds(*di, cl->pos);
464                     pushed = true;
465                     break;
466                 }
467             }
468
469             if (!pushed && actor_at(cl->pos) && mons_aligned(mon, actor_at(cl->pos)))
470             {
471                 env.cloud[cloud_list[i]].decay =
472                         env.cloud[cloud_list[i]].decay / 2 - 20;
473             }
474         }
475     }
476
477     // Now give a ranged accuracy boost to nearby allies
478     for (monster_near_iterator mi(mon, LOS_NO_TRANS); mi; ++mi)
479     {
480         if (distance2(mon->pos(), mi->pos()) >= 33 || !mons_aligned(mon, *mi))
481             continue;
482
483         if (!mi->has_ench(ENCH_WIND_AIDED))
484             mi->add_ench(mon_enchant(ENCH_WIND_AIDED, 1, mon, 20));
485         else
486         {
487             mon_enchant aid = mi->get_ench(ENCH_WIND_AIDED);
488             aid.duration = 20;
489             mi->update_ench(aid);
490         }
491     }
492 }