1 /**
2 * @file
3 * @brief Functions related to clouds.
4 *
5 * Creating a cloud module so all the cloud stuff can be isolated.
6 **/
7
8 #include "AppHdr.h"
9
10 #include "cloud.h"
11
12 #include <algorithm>
13
14 #include "areas.h"
15 #include "art-enum.h"
16 #include "colour.h"
17 #include "coordit.h"
18 #include "dungeon.h"
19 #include "english.h"
20 #include "god-conduct.h"
21 #include "god-passive.h"
22 #include "level-state-type.h"
23 #include "libutil.h" // testbits
24 #include "los.h"
25 #include "mapmark.h"
26 #include "map-knowledge.h"
27 #include "melee-attack.h"
28 #include "message.h"
29 #include "mon-behv.h"
30 #include "mon-death.h"
31 #include "mon-place.h"
32 #include "nearby-danger.h" // Compass (for random_walk, CloudGenerator)
33 #include "religion.h"
34 #include "shout.h"
35 #include "spl-util.h"
36 #include "state.h"
37 #include "stringutil.h"
38 #include "tag-version.h"
39 #include "terrain.h"
40 #include "rltiles/tiledef-main.h"
41 #include "unwind.h"
42
cloud_at(coord_def pos)43 cloud_struct* cloud_at(coord_def pos)
44 {
45 return map_find(env.cloud, pos);
46 }
47
48 /// damage = base + random2avg(random, random/15 + 1)
49 struct cloud_damage
50 {
51 int base; ///< Flat damage on every hit, pre-defenses.
52 int random; ///< Damage rolled on hit.
53 bool extra_player_dam; //< HACK: does 4+random2(8) extra damage to players.
54 // Yes, we really hate players, damn their guts.
55 };
56
57 /// Damage for most damaging clouds.
58 static const cloud_damage NORMAL_CLOUD_DAM = { 6, 16, true };
59 // 6+r2a(16,2) for monsters, 10+r2a(23,2) for players
60
61 /// A portrait of a cloud_type.
62 struct cloud_data
63 {
64 /// A (relatively) short name for the cloud. May be referenced from lua.
65 const char* terse_name;
66 /// Another name for the cloud. If nullptr, defaults to terse name.
67 const char* verbose_name;
68 /// The colour of the cloud in console.
69 colour_t colour;
70 /// Info for calculating cloud tiles.
71 cloud_tile_info tile_info;
72 /// The associated "beam" (effect) for this cloud type.
73 beam_type beam_effect;
74 /// How much damage the cloud does before defenses & resists.
75 cloud_damage damage;
76 /// Do multiple squares of this cloud block LOS?
77 bool opaque;
78 };
79
80 /// A map from cloud_type to cloud_data.
81 static const cloud_data clouds[] = {
82 // CLOUD_NONE,
83 { "?", "?", // terse, verbose name
84 },
85 // CLOUD_FIRE,
86 { "flame", "blazing flames", // terse, verbose name
87 COLOUR_UNDEF, // colour
88 { TILE_CLOUD_FIRE, CTVARY_DUR }, // tile
89 BEAM_FIRE, // beam_effect
90 NORMAL_CLOUD_DAM, // base, random damage
91 },
92 // CLOUD_MEPHITIC,
93 { "noxious fumes", nullptr, // terse, verbose name
94 GREEN, // colour
95 { TILE_CLOUD_MEPHITIC, CTVARY_DUR }, // tile
96 BEAM_MEPHITIC, // beam_effect
97 {0, 3}, // base, random damage
98 },
99 // CLOUD_COLD,
100 { "freezing vapour", "freezing vapours", // terse, verbose name
101 COLOUR_UNDEF, // colour
102 { TILE_CLOUD_COLD, CTVARY_DUR }, // tile
103 BEAM_COLD, // beam_effect
104 NORMAL_CLOUD_DAM, // base, random damage
105 },
106 // CLOUD_POISON,
107 { "poison gas", nullptr, // terse, verbose name
108 LIGHTGREEN, // colour
109 { TILE_CLOUD_POISON, CTVARY_DUR }, // tile
110 BEAM_POISON, // beam_effect
111 {0, 10}, // base, random damage
112 },
113 // CLOUD_BLACK_SMOKE,
114 { "black smoke", nullptr, // terse, verbose name
115 DARKGREY, // colour
116 { TILE_CLOUD_BLACK_SMOKE, CTVARY_NONE }, // tile
117 BEAM_NONE, {}, // beam & damage
118 true, // opacity
119 },
120 // CLOUD_GREY_SMOKE,
121 { "grey smoke", nullptr, // terse, verbose name
122 LIGHTGREY, // colour
123 { TILE_CLOUD_GREY_SMOKE, CTVARY_NONE }, // tile
124 BEAM_NONE, {}, // beam & damage
125 true, // opacity
126 },
127 // CLOUD_BLUE_SMOKE,
128 { "blue smoke", nullptr, // terse, verbose name
129 LIGHTBLUE, // colour
130 { TILE_CLOUD_BLUE_SMOKE, CTVARY_NONE }, // tile
131 BEAM_NONE, {}, // beam & damage
132 true, // opacity
133 },
134 // CLOUD_PURPLE_SMOKE,
135 { "purple smoke", nullptr, // terse, verbose name
136 MAGENTA, // colour
137 { TILE_CLOUD_TLOC_ENERGY, CTVARY_NONE }, // tile
138 BEAM_NONE, {}, // beam & damage
139 true, // opacity
140 },
141 // CLOUD_TLOC_ENERGY,
142 { "translocational energy", nullptr, // terse, verbose name
143 MAGENTA, // colour
144 { TILE_CLOUD_TLOC_ENERGY, CTVARY_NONE }, // tile
145 BEAM_NONE, {}, // beam & damage
146 true, // opacity
147 },
148 // CLOUD_FOREST_FIRE,
149 { "spreading flames", "a forest fire", // terse, verbose name
150 COLOUR_UNDEF, // colour
151 { TILE_CLOUD_FOREST_FIRE }, // tile
152 BEAM_FIRE, // beam_effect
153 NORMAL_CLOUD_DAM, // base, random damage
154 },
155 // CLOUD_STEAM,
156 { "steam", "a cloud of scalding steam", // terse, verbose name
157 LIGHTGREY, // colour
158 { TILE_CLOUD_GREY_SMOKE, CTVARY_NONE }, // tile
159 BEAM_STEAM, // beam_effect
160 {0, 16}, // base, random damage
161 true, // opacity
162 },
163 #if TAG_MAJOR_VERSION == 34
164 // CLOUD_GLOOM,
165 { "gloom", "thick gloom", // terse, verbose name
166 MAGENTA, // colour
167 { TILE_CLOUD_GLOOM }, // tile
168 },
169 #endif
170 // CLOUD_INK,
171 { "ink", nullptr, // terse, verbose name
172 DARKGREY, // colour
173 { TILE_CLOUD_INK }, // tile
174 BEAM_NONE, {}, // beam_effect & damage
175 true, // opacity
176 },
177 // CLOUD_PETRIFY,
178 { "calcifying dust", nullptr, // terse, verbose name
179 WHITE, // colour
180 { TILE_CLOUD_PETRIFY, CTVARY_RANDOM }, // tile
181 BEAM_PETRIFYING_CLOUD, {}, // beam_effect & damage
182 true, // opacity
183 },
184 // CLOUD_HOLY,
185 { "blessed fire", nullptr, // terse, verbose name
186 ETC_HOLY, // colour
187 { TILE_CLOUD_YELLOW_SMOKE }, // tile
188 BEAM_HOLY, // beam_effect
189 {4, 12, true}, // base, random damage
190 true, // opacity
191 },
192 // CLOUD_MIASMA,
193 { "foul pestilence", "dark miasma", // terse, verbose name
194 DARKGREY, // colour
195 { TILE_CLOUD_MIASMA, CTVARY_DUR }, // tile
196 BEAM_MIASMA, // beam_effect
197 { 0, 12 }, // base, random damage
198 },
199 // CLOUD_MIST,
200 { "thin mist", nullptr, // terse, verbose name
201 ETC_MIST, // colour
202 { TILE_CLOUD_MIST, CTVARY_NONE }, // tile
203 },
204 // CLOUD_CHAOS,
205 { "seething chaos", nullptr, // terse, verbose name
206 ETC_RANDOM, // colour
207 { TILE_CLOUD_CHAOS, CTVARY_RANDOM }, // tile
208 BEAM_CHAOS, // beam_effect
209 },
210 // CLOUD_RAIN,
211 { "rain", "the rain", // terse, verbose name
212 ETC_MIST, // colour
213 { TILE_CLOUD_RAIN, CTVARY_RANDOM }, // tile
214 BEAM_NONE, // unused
215 { 0, 9 }, // base, random damage
216 // but only for fiery mons
217 },
218 // CLOUD_MUTAGENIC,
219 { "mutagenic fog", nullptr, // terse, verbose name
220 ETC_MUTAGENIC, // colour
221 { TILE_ERROR, CTVARY_NONE }, // tile
222 },
223 // CLOUD_MAGIC_TRAIL,
224 { "magical condensation", nullptr, // terse, verbose name
225 ETC_MAGIC, // colour
226 { TILE_CLOUD_MAGIC_TRAIL, CTVARY_DUR }, // tile
227 },
228 // CLOUD_VORTEX,
229 { "whirling frost", nullptr, // terse, verbose name
230 ETC_VORTEX, // colour
231 { TILE_ERROR }, // tile
232 },
233 // CLOUD_DUST,
234 { "sparse dust", nullptr, // terse, verbose name
235 ETC_EARTH, // colour
236 { TILE_CLOUD_DUST, CTVARY_DUR }, // tile
237 },
238 // CLOUD_SPECTRAL,
239 { "spectral mist", nullptr, // terse, verbose name
240 ETC_ELECTRICITY, // colour
241 { TILE_CLOUD_SPECTRAL, CTVARY_DUR }, // tile
242 BEAM_NONE, // beam_effect
243 { 4, 15 }, // base, random damage
244 },
245 // CLOUD_ACID,
246 { "acidic fog", nullptr, // terse, verbose name
247 YELLOW, // colour
248 { TILE_CLOUD_ACID, CTVARY_DUR }, // dur
249 BEAM_ACID, // beam_effect
250 NORMAL_CLOUD_DAM, // base, random damage
251 },
252 // CLOUD_STORM,
253 { "thunder", "a thunderstorm", // terse, verbose name
254 ETC_DARK, // colour
255 { TILE_CLOUD_STORM, CTVARY_RANDOM }, // tile
256 BEAM_ELECTRICITY, // beam_effect
257 {12, 12},
258 },
259 // CLOUD_NEGATIVE_ENERGY,
260 { "negative energy", nullptr, // terse, verbose name
261 ETC_INCARNADINE, // colour
262 { TILE_CLOUD_NEG, CTVARY_DUR }, // tile
263 BEAM_NEG, // beam_effect
264 NORMAL_CLOUD_DAM, // base, random damage
265 },
266 // CLOUD_FLUFFY,
267 { "white fluffiness", nullptr, // terse, verbose name
268 WHITE, // colour
269 { TILE_CLOUD_WHITE_SMOKE, CTVARY_NONE }, // tile
270 BEAM_NONE, {}, // beam & damage
271 true, // opacity
272 },
273 // CLOUD_XOM_TRAIL,
274 { "magical condensation", nullptr, // terse, verbose name
275 ETC_RANDOM, // colour
276 { TILE_CLOUD_MAGIC_TRAIL, CTVARY_DUR }, // tile
277 // TODO: another tile?
278 },
279 // CLOUD_SALT,
280 { "salt", nullptr, // terse, verbose name
281 ETC_AIR, // colour
282 { TILE_CLOUD_WHITE_SMOKE, CTVARY_NONE }, // tile
283 BEAM_NONE, {}, // beam & damage
284 true, // opacity
285 },
286 // CLOUD_GOLD_DUST,
287 { "golden dust", nullptr, // terse, verbose name
288 ETC_HOLY, // colour
289 { TILE_CLOUD_GOLD_DUST, CTVARY_DUR }, // tile
290 BEAM_NONE, {}, // beam & damage
291 true, // opacity
292 },
293 // CLOUD_EMBERS,
294 { "smouldering embers", "embers",
295 ETC_SMOKE,
296 { TILE_CLOUD_BLACK_SMOKE, CTVARY_NONE },
297 },
298 // CLOUD_FLAME,
299 { "wisps of flame", nullptr, // terse, verbose name
300 ETC_FIRE, // colour
301 { TILE_CLOUD_FLAME, CTVARY_RANDOM }, // tile
302 },
303 };
304 COMPILE_CHECK(ARRAYSZ(clouds) == NUM_CLOUD_TYPES);
305
306 static int _actor_cloud_damage(const actor *act, const cloud_struct &cloud,
307 bool maximum_damage);
308
_actual_spread_rate(cloud_type type,int spread_rate)309 static int _actual_spread_rate(cloud_type type, int spread_rate)
310 {
311 if (spread_rate >= 0)
312 return spread_rate;
313
314 switch (type)
315 {
316 #if TAG_MAJOR_VERSION == 34
317 case CLOUD_GLOOM:
318 return 50;
319 #endif
320 case CLOUD_STEAM:
321 case CLOUD_GREY_SMOKE:
322 case CLOUD_BLACK_SMOKE:
323 case CLOUD_PURPLE_SMOKE:
324 case CLOUD_BLUE_SMOKE:
325 case CLOUD_FLUFFY:
326 return 22;
327 case CLOUD_RAIN:
328 case CLOUD_INK:
329 return 11;
330 default:
331 return 0;
332 }
333 }
334
_cloud2beam(cloud_type flavour)335 static beam_type _cloud2beam(cloud_type flavour)
336 {
337 if (flavour == CLOUD_RANDOM)
338 return BEAM_RANDOM;
339 return clouds[flavour].beam_effect;
340 }
341
342 #ifdef ASSERTS
_killer_whose_match(kill_category whose,killer_type killer)343 static bool _killer_whose_match(kill_category whose, killer_type killer)
344 {
345 switch (whose)
346 {
347 case KC_YOU:
348 return killer == KILL_YOU_MISSILE || killer == KILL_YOU_CONF;
349
350 case KC_FRIENDLY:
351 return killer == KILL_MON_MISSILE || killer == KILL_YOU_CONF
352 || killer == KILL_MON;
353
354 case KC_OTHER:
355 return killer == KILL_MON_MISSILE || killer == KILL_MISCAST
356 || killer == KILL_MISC || killer == KILL_MON;
357
358 case KC_NCATEGORIES:
359 die("kill category not matching killer type");
360 }
361 return false;
362 }
363 #endif
364
365 /*
366 * The LOS may have changed based on cloud changes at position `p`.
367 *
368 * @param p The position that may have changed.
369 * @param t The cloud type now there; CLOUD_NONE if there is no cloud there now.
370 * @param old The cloud type that was there; CLOUD_NONE if the was none.
371 */
_los_cloud_changed(const coord_def & p,const cloud_type t,const cloud_type old)372 static void _los_cloud_changed(const coord_def& p, const cloud_type t, const cloud_type old)
373 {
374 if (is_opaque_cloud(t) || is_opaque_cloud(old))
375 los_terrain_changed(p);
376 }
377
cloud_struct(coord_def p,cloud_type c,int d,int spread,kill_category kc,killer_type kt,mid_t src,int excl)378 cloud_struct::cloud_struct(coord_def p, cloud_type c, int d, int spread,
379 kill_category kc, killer_type kt, mid_t src,
380 int excl)
381 : pos(p), type(c), decay(d), spread_rate(spread), whose(kc), killer(kt),
382 source(src), excl_rad(excl)
383 {
384 ASSERT(_killer_whose_match(whose, killer));
385
386 if (type == CLOUD_RANDOM_SMOKE)
387 type = random_smoke_type();
388 }
389
_spread_cloud(const cloud_struct & cloud)390 static int _spread_cloud(const cloud_struct &cloud)
391 {
392 const int spreadch = cloud.decay > 30 ? 80 :
393 cloud.decay > 20 ? 50 :
394 30;
395 int extra_decay = 0;
396 for (adjacent_iterator ai(cloud.pos); ai; ++ai)
397 {
398 if (random2(100) >= spreadch)
399 continue;
400
401 if (!in_bounds(*ai)
402 || cloud_at(*ai)
403 || cell_is_solid(*ai)
404 || is_sanctuary(*ai) && !is_harmless_cloud(cloud.type))
405 {
406 continue;
407 }
408
409 if (cloud.type == CLOUD_INK && !feat_is_watery(env.grid(*ai)))
410 continue;
411
412 int newdecay = cloud.decay / 2 + 1;
413 if (newdecay >= cloud.decay)
414 newdecay = cloud.decay - 1;
415
416 env.cloud[*ai] = cloud;
417 env.cloud[*ai].pos = *ai;
418 env.cloud[*ai].decay = newdecay;
419 _los_cloud_changed(env.cloud[*ai].pos, env.cloud[*ai].type, CLOUD_NONE);
420
421 extra_decay += 8;
422 }
423
424 return extra_decay;
425 }
426
_spread_fire(const cloud_struct & cloud)427 static void _spread_fire(const cloud_struct &cloud)
428 {
429 int make_flames = one_chance_in(5);
430
431 for (adjacent_iterator ai(cloud.pos); ai; ++ai)
432 {
433 if (!in_bounds(*ai)
434 || cloud_at(*ai)
435 || is_sanctuary(*ai))
436 {
437 continue;
438 }
439
440 // burning trees produce flames all around
441 if (!cell_is_solid(*ai) && make_flames)
442 {
443 env.cloud[*ai] = cloud;
444 env.cloud[*ai].type = CLOUD_FIRE;
445 env.cloud[*ai].pos = *ai;
446 env.cloud[*ai].decay = cloud.decay / 2 + 1;
447 }
448
449 // forest fire doesn't spread in all directions at once,
450 // every neighbouring square gets a separate roll
451 if (!feat_is_flammable(env.grid(*ai)) || is_temp_terrain(*ai)
452 || x_chance_in_y(19, 20))
453 {
454 continue;
455 }
456
457 if (env.markers.property_at(*ai, MAT_ANY, "veto_destroy") == "veto")
458 continue;
459
460 if (you.see_cell(*ai))
461 mpr("The forest fire spreads!");
462 destroy_wall(*ai);
463 env.cloud[*ai] = cloud;
464 env.cloud[*ai].pos = *ai;
465 env.cloud[*ai].decay = random2(30) + 25;
466 if (cloud.whose == KC_YOU)
467 did_god_conduct(DID_KILL_PLANT, 1);
468 else if (cloud.whose == KC_FRIENDLY && !crawl_state.game_is_arena())
469 did_god_conduct(DID_KILL_PLANT, 1);
470
471 }
472 }
473
_cloud_interacts_with_terrain(const cloud_struct & cloud)474 static void _cloud_interacts_with_terrain(const cloud_struct &cloud)
475 {
476 if (cloud.type != CLOUD_FIRE && cloud.type != CLOUD_FOREST_FIRE)
477 return;
478
479 for (adjacent_iterator ai(cloud.pos); ai; ++ai)
480 {
481 const coord_def p(*ai);
482 if (in_bounds(p)
483 && feat_is_watery(env.grid(p))
484 && !cell_is_solid(p)
485 && !cloud_at(p)
486 && one_chance_in(14))
487 {
488 const cloud_type old = cloud_type_at(p);
489 env.cloud[p] = cloud_struct(p, CLOUD_STEAM, 2 + random2(5),
490 11, cloud.whose, cloud.killer,
491 cloud.source, -1);
492 _los_cloud_changed(p, env.cloud[p].type, old);
493 }
494 }
495 }
496
497 /**
498 * Convert timing out embers to conjured flames.
499 *
500 * @param cloud The cloud in question.
501 * @return Whether a flame cloud has been created.
502 */
_handle_conjure_flame(const cloud_struct & cloud)503 static bool _handle_conjure_flame(const cloud_struct &cloud)
504 {
505 if (cloud.type != CLOUD_EMBERS)
506 return false;
507
508 if (you.pos() == cloud.pos)
509 {
510 mpr("You smother the flame.");
511 return false;
512 }
513 else if (monster_at(cloud.pos))
514 {
515 mprf("%s smothers the flame.",
516 monster_at(cloud.pos)->name(DESC_THE).c_str());
517 return false;
518 }
519 else
520 {
521 mpr("The fire ignites!");
522 place_cloud(CLOUD_FIRE, cloud.pos, you.props["cflame_dur"], &you);
523 return true;
524 }
525 }
526
527 /**
528 * How fast should a given cloud fade away this turn?
529 *
530 * @param cloud_idx The cloud in question.
531 * @return The rate at which the cloud's "decay" should decrease
532 * this turn.
533 */
_cloud_dissipation_rate(const cloud_struct & cloud)534 static int _cloud_dissipation_rate(const cloud_struct &cloud)
535 {
536 int dissipate = you.time_taken;
537
538 // Player-created non-opaque clouds vanish instantly when outside LOS.
539 // (Opaque clouds don't to prevent cloud suicide.)
540 if ((cloud.source == MID_PLAYER || cloud.source == MID_YOU_FAULTLESS)
541 && !you.see_cell_no_trans(cloud.pos)
542 && !is_opaque_cloud(cloud.type))
543 {
544 return cloud.decay;
545 }
546
547 // Ink cloud shouldn't appear outside of water.
548 if (cloud.type == CLOUD_INK && !feat_is_watery(env.grid(cloud.pos)))
549 return cloud.decay;
550
551 return dissipate;
552 }
553
_dissipate_cloud(cloud_struct & cloud)554 static void _dissipate_cloud(cloud_struct& cloud)
555 {
556 // Apply calculated rate to the actual cloud.
557 cloud.decay -= _cloud_dissipation_rate(cloud);
558
559 if (cloud.type == CLOUD_FOREST_FIRE)
560 _spread_fire(cloud);
561 else if (x_chance_in_y(cloud.spread_rate, 100))
562 {
563 cloud.spread_rate -= div_rand_round(cloud.spread_rate, 10);
564 cloud.decay -= _spread_cloud(cloud);
565 }
566
567 // Check for total dissipation and handle accordingly.
568 if (cloud.decay < 1 && !_handle_conjure_flame(cloud))
569 delete_cloud(cloud.pos);
570 }
571
_handle_spectral_cloud(const cloud_struct & cloud)572 static void _handle_spectral_cloud(const cloud_struct& cloud)
573 {
574 if (actor_at(cloud.pos) || !actor_by_mid(cloud.source))
575 return;
576
577 int countn = 0;
578 for (distance_iterator di(cloud.pos, false, false, 2); di; ++di)
579 {
580 if (monster_at(*di) && monster_at(*di)->type == MONS_SPECTRAL_THING)
581 countn++;
582 }
583
584 int rate[5] = {650, 175, 45, 20, 0};
585 int chance = rate[(min(4, countn))];
586
587 if (!x_chance_in_y(chance, you.time_taken * 600))
588 return;
589
590 monster_type basetype =
591 random_choose_weighted(4, MONS_ANACONDA,
592 6, MONS_HYDRA,
593 3, MONS_SNAPPING_TURTLE,
594 2, MONS_ALLIGATOR_SNAPPING_TURTLE,
595 100, RANDOM_MONSTER);
596
597 monster* agent = monster_by_mid(cloud.source);
598 create_monster(mgen_data(MONS_SPECTRAL_THING,
599 (cloud.whose == KC_OTHER ?
600 BEH_HOSTILE :
601 BEH_FRIENDLY), cloud.pos,
602 (agent ? agent->foe : short{MHITYOU}),
603 MG_FORCE_PLACE)
604 .set_base(basetype)
605 .set_summoned(actor_by_mid(cloud.source), 1,
606 SPELL_SPECTRAL_CLOUD));
607 }
608
manage_clouds()609 void manage_clouds()
610 {
611 // We can't iterate over env.cloud directly because _dissipate_cloud
612 // will remove this cloud and invalidate our iterator.
613 vector<cloud_struct *> cloud_ptrs;
614 for (auto& entry : env.cloud)
615 cloud_ptrs.push_back(&entry.second);
616
617 for (auto ptr : cloud_ptrs)
618 {
619 cloud_struct& cloud = *ptr;
620
621 #ifdef ASSERTS
622 if (cell_is_solid(cloud.pos))
623 {
624 die("cloud %s in %s at (%d,%d)", cloud_type_name(cloud.type).c_str(),
625 dungeon_feature_name(env.grid(cloud.pos)), cloud.pos.x, cloud.pos.y);
626 }
627 #endif
628
629 if (cloud.type == CLOUD_SPECTRAL)
630 _handle_spectral_cloud(cloud);
631
632 _cloud_interacts_with_terrain(cloud);
633
634 _dissipate_cloud(cloud);
635 }
636
637 update_cloud_knowledge();
638 }
639
_maybe_leave_water(const coord_def pos)640 static void _maybe_leave_water(const coord_def pos)
641 {
642 ASSERT_IN_BOUNDS(pos);
643
644 // Rain clouds can occasionally leave shallow water or deepen it:
645 // If we're near lava, chance of leaving water is lower;
646 // if we're near deep water already, chance of leaving water
647 // is slightly higher.
648 if (!one_chance_in((5 + count_neighbours(pos, DNGN_LAVA)) -
649 count_neighbours(pos, DNGN_DEEP_WATER)))
650 {
651 return;
652 }
653
654 dungeon_feature_type feat = env.grid(pos);
655
656 if (env.grid(pos) == DNGN_FLOOR)
657 feat = DNGN_SHALLOW_WATER;
658 else if (env.grid(pos) == DNGN_SHALLOW_WATER && you.pos() != pos
659 && one_chance_in(3) && !crawl_state.game_is_sprint())
660 {
661 // Don't drown the player!
662 feat = DNGN_DEEP_WATER;
663 }
664
665 if (env.grid(pos) != feat)
666 {
667 if (you.pos() == pos && you.ground_level())
668 mpr("The rain has left you waist-deep in water!");
669 temp_change_terrain(pos, feat, random_range(500, 1000),
670 TERRAIN_CHANGE_FLOOD);
671 }
672 }
673
delete_cloud(coord_def p)674 void delete_cloud(coord_def p)
675 {
676 if (!cloud_at(p))
677 return;
678 const cloud_type type = cloud_at(p)->type;
679 env.cloud.erase(p);
680 if (type == CLOUD_RAIN)
681 _maybe_leave_water(p);
682 _los_cloud_changed(p, CLOUD_NONE, type);
683 }
684
delete_all_clouds()685 void delete_all_clouds()
686 {
687 // We can't iterate over env.cloud directly because delete_cloud
688 // will remove this cloud and invalidate our iterator.
689 vector<coord_def> cloud_locs;
690 for (auto& entry : env.cloud)
691 cloud_locs.push_back(entry.first);
692
693 for (auto pos : cloud_locs)
694 delete_cloud(pos);
695 }
696
697 // The current use of this function is for shifting in the abyss, so
698 // that clouds get moved along with the rest of the map.
move_cloud(coord_def src,coord_def newpos)699 void move_cloud(coord_def src, coord_def newpos)
700 {
701 if (!cloud_at(src))
702 return;
703 ASSERT(!cell_is_solid(newpos));
704
705 const cloud_type old = cloud_type_at(newpos);
706
707 env.cloud[newpos] = env.cloud[src];
708 env.cloud.erase(src);
709 env.cloud[newpos].pos = newpos;
710 _los_cloud_changed(src, CLOUD_NONE, env.cloud[newpos].type);
711 _los_cloud_changed(newpos, env.cloud[newpos].type, old);
712 }
713
swap_clouds(coord_def p1,coord_def p2)714 void swap_clouds(coord_def p1, coord_def p2)
715 {
716 if (p1 == p2)
717 return;
718 if (!cloud_at(p1))
719 {
720 move_cloud(p2, p1);
721 return;
722 }
723 else if (!cloud_at(p2))
724 {
725 move_cloud(p1, p2);
726 return;
727 }
728
729 cloud_struct temp = env.cloud[p1];
730 env.cloud[p1] = env.cloud[p2];
731 env.cloud[p2] = temp;
732 env.cloud[p1].pos = p1;
733 env.cloud[p2].pos = p2;
734 _los_cloud_changed(p1, env.cloud[p1].type, env.cloud[p2].type);
735 _los_cloud_changed(p2, env.cloud[p2].type, env.cloud[p1].type);
736 }
737
738 // Places a cloud with the given stats assuming one doesn't already
739 // exist at that point.
check_place_cloud(cloud_type cl_type,const coord_def & p,int lifetime,const actor * agent,int spread_rate,int excl_rad)740 void check_place_cloud(cloud_type cl_type, const coord_def& p, int lifetime,
741 const actor *agent, int spread_rate, int excl_rad)
742 {
743 if (!in_bounds(p) || cloud_at(p))
744 return;
745
746 place_cloud(cl_type, p, lifetime, agent, spread_rate, excl_rad);
747 }
748
_cloud_is_stronger(cloud_type ct,const cloud_struct & cloud)749 static bool _cloud_is_stronger(cloud_type ct, const cloud_struct& cloud)
750 {
751 return (is_harmless_cloud(cloud.type) &&
752 (!is_opaque_cloud(cloud.type) || is_opaque_cloud(ct)))
753 || cloud.type == CLOUD_STEAM
754 || ct == CLOUD_VORTEX; // soon gone
755 }
756
757 /*
758 * Places a cloud with the given stats. Will overwrite an old cloud under some
759 * circumstances.
760 *
761 * @param cl_type The type of cloud to place.
762 * @param ctarget The location of the cloud.
763 * @param cl_range How many turns the cloud will take to decay.
764 * @param agent Any agent that may have caused the cloud. If this is the
765 * player, god conducts are applied.
766 * @param spread_rate How quickly the cloud spreads.
767 * @param excl_rad How large of an exclusion radius to make around the
768 * cloud.
769 * @param do_conducts If true, apply any relevant god conducts for flame
770 * placement.
771 */
place_cloud(cloud_type cl_type,const coord_def & ctarget,int cl_range,const actor * agent,int spread_rate,int excl_rad,bool do_conducts)772 void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range,
773 const actor *agent, int spread_rate, int excl_rad,
774 bool do_conducts)
775 {
776 if (is_sanctuary(ctarget) && !is_harmless_cloud(cl_type))
777 return;
778
779 if (cl_type == CLOUD_INK && !feat_is_watery(env.grid(ctarget)))
780 return;
781
782 if (env.level_state & LSTATE_STILL_WINDS
783 && cl_type != CLOUD_VORTEX
784 && cl_type != CLOUD_INK)
785 {
786 return;
787 }
788
789 const monster * const mons = monster_at(ctarget);
790
791 // Fedhas protects plants from damaging clouds.
792 // XX demonic guardians? This logic mostly doesn't apply because protected
793 // monsters are also cloud immune, mostly
794 if (god_protects(agent, mons)
795 && !actor_cloud_immune(*mons, cl_type))
796 {
797 return;
798 }
799
800 ASSERT(!cell_is_solid(ctarget));
801
802 god_conduct_trigger conducts[3];
803 kill_category whose = KC_OTHER;
804 killer_type killer = KILL_MISC;
805 mid_t source = MID_NOBODY;
806 if (agent && agent->is_player())
807 {
808 if (do_conducts
809 && mons && mons->alive()
810 && !actor_cloud_immune(*mons, cl_type))
811 {
812 set_attack_conducts(conducts, *mons, you.can_see(*mons));
813 }
814
815 whose = KC_YOU;
816 killer = KILL_YOU_MISSILE;
817 source = MID_PLAYER;
818 }
819 else if (agent && agent->is_monster())
820 {
821 if (agent->as_monster()->friendly())
822 whose = KC_FRIENDLY;
823 else
824 whose = KC_OTHER;
825 killer = KILL_MON_MISSILE;
826 source = agent->mid;
827 }
828
829 // There's already a cloud here. See if we can overwrite it.
830 const cloud_struct *cloud = cloud_at(ctarget);
831 if (cloud && !_cloud_is_stronger(cl_type, *cloud))
832 return;
833
834 // If the old cloud was opaque, may need to recalculate los. It *is*
835 // possible to overwrite an opaque cloud with a non-opaque one; OOD will do
836 // this.
837 const cloud_type old = cloud ? cloud->type : CLOUD_NONE;
838 env.cloud[ctarget] = cloud_struct(ctarget, cl_type, cl_range * 10,
839 _actual_spread_rate(cl_type, spread_rate), whose, killer, source,
840 excl_rad);
841 _los_cloud_changed(ctarget, env.cloud[ctarget].type, old);
842 }
843
is_opaque_cloud(cloud_type ctype)844 bool is_opaque_cloud(cloud_type ctype)
845 {
846 return ctype >= CLOUD_NONE && ctype < NUM_CLOUD_TYPES
847 && clouds[ctype].opaque;
848 }
849
cloud_type_at(const coord_def & c)850 cloud_type cloud_type_at(const coord_def &c)
851 {
852 return cloud_at(c) ? cloud_at(c)->type : CLOUD_NONE;
853 }
854
cloud_is_yours_at(const coord_def & c)855 bool cloud_is_yours_at(const coord_def &c)
856 {
857 return cloud_at(c) ? YOU_KILL(cloud_at(c)->killer) : false;
858 }
859
random_smoke_type()860 cloud_type random_smoke_type()
861 {
862 return random_choose(CLOUD_GREY_SMOKE, CLOUD_BLUE_SMOKE,
863 CLOUD_BLACK_SMOKE, CLOUD_PURPLE_SMOKE);
864 }
max_cloud_damage(cloud_type cl_type,int power)865 int max_cloud_damage(cloud_type cl_type, int power)
866 {
867 cloud_struct cloud;
868 cloud.type = cl_type;
869 cloud.decay = power * 10;
870 return _actor_cloud_damage(&you, cloud, true);
871 }
872
873 // Returns true if the cloud type has negative side effects beyond
874 // plain damage and inventory destruction effects.
_cloud_has_negative_side_effects(cloud_type cloud)875 static bool _cloud_has_negative_side_effects(cloud_type cloud)
876 {
877 switch (cloud)
878 {
879 case CLOUD_MEPHITIC:
880 case CLOUD_MIASMA:
881 case CLOUD_MUTAGENIC:
882 case CLOUD_CHAOS:
883 case CLOUD_PETRIFY:
884 case CLOUD_ACID:
885 case CLOUD_NEGATIVE_ENERGY:
886 return true;
887 default:
888 return false;
889 }
890 }
891
_cloud_damage_calc(int size,int n_average,int extra,bool maximum_damage)892 static int _cloud_damage_calc(int size, int n_average, int extra,
893 bool maximum_damage)
894 {
895 return maximum_damage?
896 extra + size - 1
897 : random2avg(size, n_average) + extra;
898 }
899
_base_dam(const cloud_damage & dam,bool vs_player)900 static int _base_dam(const cloud_damage &dam, bool vs_player)
901 {
902 if (vs_player && dam.extra_player_dam)
903 return dam.base + 4;
904 return dam.base;
905 }
906
_rand_dam(const cloud_damage & dam,bool vs_player)907 static int _rand_dam(const cloud_damage &dam, bool vs_player)
908 {
909 if (vs_player && dam.extra_player_dam)
910 return dam.random + 7;
911 return dam.random;
912 }
913
914 // Calculates the base damage that the cloud does to an actor without
915 // considering resistances and time spent in the cloud.
_cloud_base_damage(const actor * act,cloud_type flavour,bool maximum_damage)916 static int _cloud_base_damage(const actor *act,
917 cloud_type flavour,
918 bool maximum_damage)
919 {
920 const cloud_damage &dam = clouds[flavour].damage;
921 const bool vs_player = act->is_player();
922 const int random_dam = _rand_dam(dam, vs_player);
923 const int base_dam = _base_dam(dam, vs_player);
924 const int trials = dam.random/15 + 1;
925
926 return _cloud_damage_calc(random_dam, trials, base_dam, maximum_damage);
927
928 }
929
930 /**
931 * Is the given actor immune to cloud damage and other negative side effects
932 * (other than opaque clouds + invis) from all clouds of the given type?
933 */
actor_cloud_immune(const actor & act,cloud_type type)934 bool actor_cloud_immune(const actor &act, cloud_type type)
935 {
936 // Qazlalites and scarfwearers get immunity to clouds.
937 // and the Cloud Mage too!
938 if (is_harmless_cloud(type) || act.cloud_immune())
939 return true;
940
941 switch (type)
942 {
943 case CLOUD_FIRE:
944 case CLOUD_FOREST_FIRE:
945 if (!act.is_player())
946 return act.res_fire() >= 3;
947 return player_equip_unrand(UNRAND_SALAMANDER)
948 #if TAG_MAJOR_VERSION == 34
949 || you.has_mutation(MUT_FLAME_CLOUD_IMMUNITY)
950 #endif
951 || player_equip_unrand(UNRAND_FIRESTARTER)
952 || you.has_mutation(MUT_IGNITE_BLOOD);
953 case CLOUD_HOLY:
954 return act.res_holy_energy() >= 3;
955 case CLOUD_COLD:
956 if (!act.is_player())
957 return act.res_cold() >= 3;
958 return player_equip_unrand(UNRAND_FROSTBITE)
959 #if TAG_MAJOR_VERSION == 34
960 || you.has_mutation(MUT_FREEZING_CLOUD_IMMUNITY)
961 #endif
962 ;
963 case CLOUD_MEPHITIC:
964 return act.res_poison() > 0;
965 case CLOUD_POISON:
966 return act.res_poison() > 0;
967 case CLOUD_STEAM:
968 return act.res_steam() > 0;
969 case CLOUD_MIASMA:
970 return act.res_miasma();
971 case CLOUD_PETRIFY:
972 return act.res_petrify();
973 case CLOUD_SPECTRAL:
974 return bool(act.holiness() & MH_UNDEAD);
975 case CLOUD_ACID:
976 return act.res_acid() > 0;
977 case CLOUD_STORM:
978 return act.res_elec() >= 3;
979 case CLOUD_NEGATIVE_ENERGY:
980 return act.res_negative_energy() >= 3;
981 case CLOUD_VORTEX:
982 return act.res_polar_vortex();
983 case CLOUD_RAIN:
984 return !act.is_fiery();
985 default:
986 return false;
987 }
988 }
989
990 // Returns true if the actor is immune to cloud damage and other negative
991 // side effects of the given cloud (other than opaque clouds + invis).
992 //
993 // Note that actor_cloud_immune may be false even if the actor will
994 // not be harmed by the cloud. The cloud may have positive
995 // side-effects on the actor.
actor_cloud_immune(const actor & act,const cloud_struct & cloud)996 bool actor_cloud_immune(const actor &act, const cloud_struct &cloud)
997 {
998 if (actor_cloud_immune(act, cloud.type))
999 return true;
1000
1001 const bool player = act.is_player();
1002
1003 if (!player
1004 && (god_protects(act.as_monster())
1005 || testbits(act.as_monster()->flags, MF_DEMONIC_GUARDIAN))
1006 && (cloud.whose == KC_YOU || cloud.whose == KC_FRIENDLY)
1007 && (act.as_monster()->friendly() || act.as_monster()->neutral())
1008 && (cloud.whose == KC_YOU || cloud.whose == KC_FRIENDLY))
1009 {
1010 return true;
1011 }
1012
1013 int summon_type = 0;
1014 act.is_summoned(nullptr, &summon_type);
1015 if (!player && have_passive(passive_t::cloud_immunity)
1016 && (act.as_monster()->friendly() && summon_type == MON_SUMM_AID))
1017 {
1018 return true;
1019 }
1020
1021 return false;
1022 }
1023
1024 // Returns a numeric resistance value for the actor's resistance to
1025 // the cloud's effects. If the actor is immune to the cloud's damage,
1026 // returns WILL_INVULN.
_actor_cloud_resist(const actor * act,const cloud_struct & cloud)1027 static int _actor_cloud_resist(const actor *act, const cloud_struct &cloud)
1028 {
1029 if (actor_cloud_immune(*act, cloud))
1030 return WILL_INVULN;
1031 switch (cloud.type)
1032 {
1033 case CLOUD_RAIN:
1034 return act->is_fiery()? 0 : WILL_INVULN;
1035 case CLOUD_FIRE:
1036 case CLOUD_FOREST_FIRE:
1037 return act->res_fire();
1038 case CLOUD_HOLY:
1039 return act->res_holy_energy();
1040 case CLOUD_COLD:
1041 return act->res_cold();
1042 case CLOUD_PETRIFY:
1043 return act->res_petrify();
1044 case CLOUD_ACID:
1045 return act->res_acid();
1046 case CLOUD_STORM:
1047 return act->res_elec();
1048 case CLOUD_NEGATIVE_ENERGY:
1049 return act->res_negative_energy();
1050
1051 default:
1052 return 0;
1053 }
1054 }
1055
_mephitic_cloud_roll(const monster * mons)1056 static bool _mephitic_cloud_roll(const monster* mons)
1057 {
1058 return mons->get_hit_dice() >= MEPH_HD_CAP ? one_chance_in(50)
1059 : !x_chance_in_y(mons->get_hit_dice(), MEPH_HD_CAP);
1060 }
1061
1062 // Applies cloud messages and side-effects and returns true if the
1063 // cloud had a side-effect. This function does not check for cloud immunity.
1064 // ... but it's only called if the actor isn't immune
_actor_apply_cloud_side_effects(actor * act,const cloud_struct & cloud,int final_damage)1065 static bool _actor_apply_cloud_side_effects(actor *act,
1066 const cloud_struct &cloud,
1067 int final_damage)
1068 {
1069 ASSERT(act); // XXX: change to actor &act
1070 const bool player = act->is_player();
1071 monster *mons = !player? act->as_monster() : nullptr;
1072 switch (cloud.type)
1073 {
1074 case CLOUD_FIRE:
1075 case CLOUD_STEAM:
1076 if (player)
1077 maybe_melt_player_enchantments(BEAM_FIRE, final_damage);
1078 case CLOUD_RAIN:
1079 case CLOUD_STORM:
1080 if (act->is_fiery() && final_damage > 0)
1081 {
1082 if (you.can_see(*act))
1083 {
1084 mprf("%s %s in the rain.",
1085 act->name(DESC_THE).c_str(),
1086 act->conj_verb(silenced(act->pos())?
1087 "steam" : "sizzle").c_str());
1088 }
1089 }
1090 break;
1091
1092 case CLOUD_MEPHITIC:
1093 {
1094 if (player)
1095 {
1096 if (1 + random2(27) >= you.experience_level)
1097 {
1098 mpr("You choke on the stench!");
1099 // effectively one or two turns, since it will be
1100 // decremented right away
1101 confuse_player(random_range(2, 3));
1102 return true;
1103 }
1104 }
1105 else
1106 {
1107 bolt beam;
1108 beam.flavour = BEAM_CONFUSION;
1109 beam.thrower = cloud.killer;
1110
1111 if (cloud.whose == KC_FRIENDLY)
1112 beam.source_id = MID_ANON_FRIEND;
1113
1114 if (_mephitic_cloud_roll(mons))
1115 {
1116 beam.apply_enchantment_to_monster(mons);
1117 return true;
1118 }
1119 }
1120 break;
1121 }
1122
1123 case CLOUD_PETRIFY:
1124 {
1125 if (player)
1126 {
1127 if (random2(55) - 13 >= you.experience_level)
1128 {
1129 you.petrify(cloud.agent());
1130 return true;
1131 }
1132 }
1133 else
1134 {
1135 bolt beam;
1136 beam.flavour = BEAM_PETRIFY;
1137 beam.thrower = cloud.killer;
1138
1139 if (cloud.whose == KC_FRIENDLY)
1140 beam.source_id = MID_ANON_FRIEND;
1141
1142 beam.apply_enchantment_to_monster(mons);
1143 return true;
1144 }
1145 break;
1146 }
1147
1148 case CLOUD_POISON:
1149 if (player)
1150 {
1151 const actor* agent = cloud.agent();
1152 poison_player(5 + roll_dice(3, 8), agent ? agent->name(DESC_A) : "",
1153 cloud.cloud_name());
1154 }
1155 else
1156 poison_monster(mons, cloud.agent());
1157 return true;
1158
1159 case CLOUD_MIASMA:
1160 if (player)
1161 return miasma_player(cloud.agent(), cloud.cloud_name());
1162 else
1163 return miasma_monster(mons, cloud.agent());
1164
1165 case CLOUD_MUTAGENIC:
1166 if (player)
1167 {
1168 mpr("The mutagenic energy flows into you.");
1169 // It's possible that you got trampled into the mutagenic cloud
1170 // and it's not your fault... so we'll say it's not intentional.
1171 // (it's quite bad in any case, so players won't scum, probably.)
1172 contaminate_player(1300 + random2(1250), false);
1173 // min 2 turns to yellow, max 4
1174 return true;
1175 }
1176 else if (coinflip() && mons->malmutate("mutagenic cloud"))
1177 {
1178 if (you_worship(GOD_ZIN) && cloud.whose == KC_YOU)
1179 did_god_conduct(DID_DELIBERATE_MUTATING, 5 + random2(3));
1180 return true;
1181 }
1182 return false;
1183
1184 case CLOUD_CHAOS:
1185 if (coinflip())
1186 {
1187 // TODO: Not have this in melee_attack
1188 melee_attack::chaos_affect_actor(act);
1189 return true;
1190 }
1191 break;
1192
1193 case CLOUD_ACID:
1194 {
1195 const actor* agent = cloud.agent();
1196 act->splash_with_acid(agent, 5, true);
1197 return true;
1198 }
1199
1200 case CLOUD_NEGATIVE_ENERGY:
1201 {
1202 actor* agent = cloud.agent();
1203 if (act->drain(agent, final_damage))
1204 {
1205 if (cloud.whose == KC_YOU)
1206 did_god_conduct(DID_EVIL, 5 + random2(3));
1207 return true;
1208 }
1209 break;
1210 }
1211
1212 default:
1213 break;
1214 }
1215 return false;
1216 }
1217
_actor_cloud_base_damage(const actor * act,const cloud_struct & cloud,int resist,bool maximum_damage)1218 static int _actor_cloud_base_damage(const actor *act,
1219 const cloud_struct &cloud,
1220 int resist,
1221 bool maximum_damage)
1222 {
1223 if (actor_cloud_immune(*act, cloud))
1224 return 0;
1225
1226 const int cloud_raw_base_damage =
1227 _cloud_base_damage(act, cloud.type, maximum_damage);
1228 const int cloud_base_damage = (resist == WILL_INVULN ?
1229 0 : cloud_raw_base_damage);
1230 return cloud_base_damage;
1231 }
1232
_cloud_damage_output(const actor * actor,beam_type flavour,int base_damage,bool maximum_damage=false)1233 static int _cloud_damage_output(const actor *actor,
1234 beam_type flavour,
1235 int base_damage,
1236 bool maximum_damage = false)
1237 {
1238 if (maximum_damage)
1239 return resist_adjust_damage(actor, flavour, base_damage);
1240
1241 int dam = actor->apply_ac(base_damage);
1242 dam = resist_adjust_damage(actor, flavour, dam);
1243 return max(0, dam);
1244 }
1245
1246 /**
1247 * How much damage will this cloud do to the given actor?
1248 *
1249 * @param act The actor in question.
1250 * @param cloud The cloud in question.
1251 * @param maximum_damage Whether to return the maximum possible damage.
1252 */
_actor_cloud_damage(const actor * act,const cloud_struct & cloud,bool maximum_damage)1253 static int _actor_cloud_damage(const actor *act,
1254 const cloud_struct &cloud,
1255 bool maximum_damage)
1256 {
1257 const int resist = _actor_cloud_resist(act, cloud);
1258 const int cloud_base_damage = _actor_cloud_base_damage(act, cloud,
1259 resist,
1260 maximum_damage);
1261 int final_damage = cloud_base_damage;
1262
1263 switch (cloud.type)
1264 {
1265 case CLOUD_FIRE:
1266 case CLOUD_FOREST_FIRE:
1267 case CLOUD_HOLY:
1268 case CLOUD_COLD:
1269 case CLOUD_STEAM:
1270 case CLOUD_SPECTRAL:
1271 case CLOUD_ACID:
1272 case CLOUD_NEGATIVE_ENERGY:
1273 final_damage =
1274 _cloud_damage_output(act, _cloud2beam(cloud.type),
1275 cloud_base_damage,
1276 maximum_damage);
1277 break;
1278 case CLOUD_STORM:
1279 {
1280
1281 // if we don't have thunder, there's always rain
1282 cloud_struct raincloud = cloud;
1283 raincloud.type = CLOUD_RAIN;
1284 const int rain_damage = _actor_cloud_damage(act, raincloud,
1285 maximum_damage);
1286
1287 // if this isn't just a test run, and no time passed, don't trigger
1288 // lightning. (just rain.)
1289 if (!maximum_damage && !(you.turn_is_over && you.time_taken > 0))
1290 return rain_damage;
1291
1292 // only announce ourselves if this isn't a test run.
1293 if (!maximum_damage)
1294 cloud.announce_actor_engulfed(act);
1295
1296 const int turns_per_lightning = 3;
1297 const int aut_per_lightning = turns_per_lightning * BASELINE_DELAY;
1298
1299 // if we fail our lightning roll, again, just rain.
1300 if (!maximum_damage && !x_chance_in_y(you.time_taken,
1301 aut_per_lightning))
1302 {
1303 return rain_damage;
1304 }
1305
1306 const int lightning_dam = _cloud_damage_output(act,
1307 _cloud2beam(cloud.type),
1308 cloud_base_damage,
1309 maximum_damage);
1310
1311 if (maximum_damage)
1312 {
1313 // Average maximum damage over time.
1314 const int avg_dam = lightning_dam / turns_per_lightning;
1315 if (avg_dam > 0)
1316 return avg_dam;
1317 return rain_damage; // vs relec+++ or w/e
1318 }
1319
1320 if (act->is_player())
1321 mpr("You are struck by lightning!");
1322 else if (you.can_see(*act))
1323 {
1324 simple_monster_message(*act->as_monster(),
1325 " is struck by lightning.");
1326 }
1327 else if (you.see_cell(act->pos()))
1328 {
1329 mpr("Lightning from the thunderstorm strikes something you cannot "
1330 "see.");
1331 }
1332
1333 return lightning_dam;
1334
1335 }
1336 default:
1337 break;
1338 }
1339
1340 return timescale_damage(act, final_damage);
1341 }
1342
1343 // Applies damage and side effects for an actor in a cloud and returns
1344 // the damage dealt.
actor_apply_cloud(actor * act)1345 int actor_apply_cloud(actor *act)
1346 {
1347 const cloud_struct* cl = cloud_at(act->pos());
1348 if (!cl)
1349 return 0;
1350
1351 const cloud_struct &cloud(*cl);
1352 const bool player = act->is_player();
1353 monster *mons = act->as_monster();
1354 const beam_type cloud_flavour = _cloud2beam(cloud.type);
1355
1356 if (actor_cloud_immune(*act, cloud))
1357 return 0;
1358
1359 const int resist = _actor_cloud_resist(act, cloud);
1360 const int cloud_max_base_damage =
1361 _actor_cloud_base_damage(act, cloud, resist, true);
1362 const int final_damage = _actor_cloud_damage(act, cloud, false);
1363
1364 if ((player || final_damage > 0
1365 || _cloud_has_negative_side_effects(cloud.type))
1366 && cloud.type != CLOUD_STORM) // handled elsewhere
1367 {
1368 cloud.announce_actor_engulfed(act);
1369 }
1370 if (player && cloud_max_base_damage > 0 && resist > 0
1371 && (cloud.type != CLOUD_STORM || final_damage > 0))
1372 {
1373 canned_msg(MSG_YOU_RESIST);
1374 }
1375
1376 if (cloud_flavour != BEAM_NONE)
1377 act->expose_to_element(cloud_flavour, 7);
1378
1379 const bool side_effects =
1380 _actor_apply_cloud_side_effects(act, cloud, final_damage);
1381
1382 if (!player && (side_effects || final_damage > 0))
1383 behaviour_event(mons, ME_DISTURB, 0, act->pos());
1384
1385 if (final_damage)
1386 {
1387 actor *oppressor = cloud.agent();
1388 const string oppr_name =
1389 oppressor ? " "+apostrophise(oppressor->name(DESC_THE))
1390 : "";
1391 dprf("%s %s %d damage from%s cloud: %s.",
1392 act->name(DESC_THE).c_str(),
1393 act->conj_verb("take").c_str(),
1394 final_damage,
1395 oppr_name.c_str(),
1396 cloud.cloud_name().c_str());
1397
1398 act->hurt(oppressor, final_damage, BEAM_MISSILE,
1399 KILLED_BY_CLOUD, "", cloud.cloud_name(true));
1400 }
1401
1402 return final_damage;
1403 }
1404
1405 // Describe cloud damage in the form "3-18". If vs_player is set,
1406 // extra anti-player damage is included.
desc_cloud_damage(cloud_type cl_type,bool vs_player)1407 string desc_cloud_damage(cloud_type cl_type, bool vs_player)
1408 {
1409 const cloud_damage &dam_info = clouds[cl_type].damage;
1410 const int base = _base_dam(dam_info, vs_player);
1411 const int rand = _rand_dam(dam_info, vs_player);
1412 if (rand == 0) {
1413 if (base == 0)
1414 return "";
1415 return make_stringf("%d", base);
1416 }
1417 return make_stringf("%d-%d", base, base + rand - 1);
1418 }
1419
_cloud_is_harmful(actor * act,cloud_struct & cloud,int maximum_negligible_damage)1420 static bool _cloud_is_harmful(actor *act, cloud_struct &cloud,
1421 int maximum_negligible_damage)
1422 {
1423 return !actor_cloud_immune(*act, cloud)
1424 && (_cloud_has_negative_side_effects(cloud.type)
1425 || (_actor_cloud_damage(act, cloud, true) >
1426 maximum_negligible_damage));
1427 }
1428
1429 /**
1430 * Is this cloud type dangerous to you?
1431 *
1432 * @param type the type of cloud to look at.
1433 * @param accept_temp_resistances whether to look at resistances from your form
1434 * or durations; items and gods are used regardless of this parameter's value.
1435 * @param yours whether to treat this cloud as being made by you.
1436 */
is_damaging_cloud(cloud_type type,bool accept_temp_resistances,bool yours)1437 bool is_damaging_cloud(cloud_type type, bool accept_temp_resistances, bool yours)
1438 {
1439 // If you're immune to clouds, then no clouds are damaging. Bing bong so simple!
1440 if (you.cloud_immune())
1441 return false;
1442
1443 // A nasty hack; map_knowledge doesn't preserve whom the cloud belongs to.
1444 if (type == CLOUD_VORTEX)
1445 return !you.duration[DUR_VORTEX] && !you.duration[DUR_VORTEX_COOLDOWN];
1446
1447 if (accept_temp_resistances)
1448 {
1449 cloud_struct cloud;
1450 cloud.type = type;
1451 cloud.decay = 100;
1452 if (yours)
1453 cloud.set_killer(KILL_YOU);
1454 return _cloud_is_harmful(&you, cloud, 0);
1455 }
1456 else
1457 {
1458 // [ds] Yes, this is an ugly kludge: temporarily hide
1459 // durations and transforms.
1460 unwind_var<durations_t> old_durations(you.duration);
1461 unwind_var<transformation> old_form(you.form, transformation::none);
1462 you.duration.init(0);
1463 return is_damaging_cloud(type, true, yours);
1464 }
1465 }
1466
1467 /**
1468 * Will the given monster refuse to walk into the given cloud?
1469 *
1470 * @param mons The monster in question.
1471 * @param cloud The cloud in question.
1472 * @param extra_careful Whether the monster could suffer any harm from the
1473 * cloud at all, even if it would normally be brave
1474 * enough (based on e.g. hp) to enter the cloud.
1475 * @return Whether the monster is NOT ok to enter the cloud.
1476 */
_mons_avoids_cloud(const monster * mons,const cloud_struct & cloud,bool extra_careful)1477 static bool _mons_avoids_cloud(const monster* mons, const cloud_struct& cloud,
1478 bool extra_careful)
1479 {
1480 // Friendlies avoid snuffing the player's conjured flames
1481 if (mons->attitude == ATT_FRIENDLY && cloud.type == CLOUD_EMBERS)
1482 return true;
1483
1484 // clouds you're immune to are inherently safe.
1485 if (actor_cloud_immune(*mons, cloud))
1486 return false;
1487
1488 // harmless clouds, likewise.
1489 if (is_harmless_cloud(cloud.type))
1490 return false;
1491
1492 // Berserk monsters are less careful and will blindly plow through any
1493 // dangerous cloud, just to kill you. {due}
1494 if (!extra_careful && mons->berserk_or_insane())
1495 return false;
1496
1497 switch (cloud.type)
1498 {
1499 case CLOUD_MIASMA:
1500 // Even the dumbest monsters will avoid miasma if they can.
1501 return true;
1502
1503 case CLOUD_RAIN:
1504 // Fiery monsters dislike the rain.
1505 if (mons->is_fiery() && extra_careful)
1506 return true;
1507
1508 // We don't care about what's underneath the rain cloud if we can fly.
1509 if (mons->airborne())
1510 return false;
1511
1512 // These don't care about deep water.
1513 if (monster_habitable_grid(mons, DNGN_DEEP_WATER))
1514 return false;
1515
1516 // This position could become deep water, and they might drown.
1517 if (env.grid(cloud.pos) == DNGN_SHALLOW_WATER
1518 && mons_intel(*mons) > I_BRAINLESS)
1519 {
1520 return true;
1521 }
1522 break;
1523
1524 default:
1525 {
1526 if (extra_careful)
1527 return true;
1528
1529 // calc damage here instead of using _cloud_base_damage() so we can
1530 // set our own # of trials, to try to make the AI more consistent
1531 // XXX: add a param instead?
1532 const cloud_damage &dam_info = clouds[cloud.type].damage;
1533 const int base_damage = _cloud_damage_calc(dam_info.random,
1534 max(1, dam_info.random / 9),
1535 dam_info.base, false);
1536 const int damage = resist_adjust_damage(mons,
1537 clouds[cloud.type].beam_effect,
1538 base_damage);
1539 const int hp_threshold = damage * 3;
1540
1541 // intelligent monsters want a larger margin of safety
1542 const int safety_mult = (mons_intel(*mons) > I_ANIMAL) ? 2 : 1;
1543 // dare we risk the damage?
1544 const bool hp_ok = mons->hit_points > safety_mult * hp_threshold;
1545 // dare we risk the status effects?
1546 const bool sfx_ok = cloud.type != CLOUD_MEPHITIC
1547 || x_chance_in_y(mons->get_hit_dice() - 1, 5);
1548 if (hp_ok && sfx_ok)
1549 return false;
1550 break;
1551 }
1552 }
1553
1554 // Exceedingly dumb creatures will wander into harmful clouds.
1555 if (mons_intel(*mons) == I_BRAINLESS && !extra_careful)
1556 return false;
1557
1558 // If we get here, the cloud is potentially harmful.
1559 return true;
1560 }
1561
1562 // Like the above, but allow a monster to move from one damaging cloud
1563 // to another, even if they're of different types.
mons_avoids_cloud(const monster * mons,coord_def pos,bool placement)1564 bool mons_avoids_cloud(const monster* mons, coord_def pos, bool placement)
1565 {
1566 if (!cloud_at(pos))
1567 return false;
1568
1569 // Is the target cloud okay?
1570 if (!_mons_avoids_cloud(mons, *cloud_at(pos), placement))
1571 return false;
1572
1573 // If we're already in a cloud that we'd want to avoid then moving
1574 // from one to the other is okay.
1575 if (!in_bounds(mons->pos()) || mons->pos() == pos)
1576 return true;
1577
1578 if (!cloud_at(mons->pos()))
1579 return true;
1580
1581 return !_mons_avoids_cloud(mons, *cloud_at(mons->pos()), true);
1582 }
1583
is_harmless_cloud(cloud_type type)1584 bool is_harmless_cloud(cloud_type type)
1585 {
1586 return clouds[type].beam_effect == BEAM_NONE
1587 && clouds[type].damage.base == 0
1588 && clouds[type].damage.random == 0
1589 && !_cloud_has_negative_side_effects(type)
1590 && type != CLOUD_VORTEX;
1591 }
1592
cloud_type_name(cloud_type type,bool terse)1593 string cloud_type_name(cloud_type type, bool terse)
1594 {
1595 if (type <= CLOUD_NONE || type >= NUM_CLOUD_TYPES)
1596 return "buggy goodness";
1597
1598 ASSERT(clouds[type].terse_name);
1599 if (terse || clouds[type].verbose_name == nullptr)
1600 return clouds[type].terse_name;
1601 return clouds[type].verbose_name;
1602 }
1603
cloud_name_to_type(const string & name)1604 cloud_type cloud_name_to_type(const string &name)
1605 {
1606 const string lower_name = lowercase_string(name);
1607
1608 if (lower_name == "random")
1609 return CLOUD_RANDOM;
1610 else if (lower_name == "debugging")
1611 return CLOUD_DEBUGGING;
1612
1613 for (int i = CLOUD_NONE; i < CLOUD_RANDOM; i++)
1614 if (cloud_type_name(static_cast<cloud_type>(i)) == lower_name)
1615 return static_cast<cloud_type>(i);
1616
1617 return CLOUD_NONE;
1618 }
1619
random_walk(coord_def start,int dist)1620 coord_def random_walk(coord_def start, int dist)
1621 {
1622 ASSERT(in_bounds(start));
1623 ASSERT(dist >= 1);
1624
1625 int moves_left = dist;
1626 coord_def pos = start;
1627 while (moves_left-- > 0)
1628 {
1629 int okay_dirs = 0;
1630 int dir = -1;
1631 for (int j = 0; j < 8; j++)
1632 {
1633 const coord_def new_pos = pos + Compass[j];
1634
1635 if (in_bounds(new_pos) && !feat_is_solid(env.grid(new_pos))
1636 && one_chance_in(++okay_dirs))
1637 {
1638 dir = j;
1639 }
1640 }
1641
1642 if (okay_dirs == 0)
1643 break;
1644
1645 if (one_chance_in(++okay_dirs))
1646 continue;
1647
1648 pos += Compass[dir];
1649 }
1650
1651 return pos;
1652 }
1653
1654 ////////////////////////////////////////////////////////////////////////
1655 // cloud_struct
1656
killer_to_whose(killer_type _killer)1657 kill_category cloud_struct::killer_to_whose(killer_type _killer)
1658 {
1659 switch (_killer)
1660 {
1661 case KILL_YOU:
1662 case KILL_YOU_MISSILE:
1663 case KILL_YOU_CONF:
1664 return KC_YOU;
1665
1666 case KILL_MON:
1667 case KILL_MON_MISSILE:
1668 case KILL_MISC:
1669 return KC_OTHER;
1670
1671 default:
1672 die("invalid killer type");
1673 }
1674 return KC_OTHER;
1675 }
1676
whose_to_killer(kill_category _whose)1677 killer_type cloud_struct::whose_to_killer(kill_category _whose)
1678 {
1679 switch (_whose)
1680 {
1681 case KC_YOU: return KILL_YOU_MISSILE;
1682 case KC_FRIENDLY: return KILL_MON_MISSILE;
1683 case KC_OTHER: return KILL_MISC;
1684 case KC_NCATEGORIES: die("invalid kill category");
1685 }
1686 return KILL_NONE;
1687 }
1688
set_whose(kill_category _whose)1689 void cloud_struct::set_whose(kill_category _whose)
1690 {
1691 whose = _whose;
1692 killer = whose_to_killer(whose);
1693 }
1694
set_killer(killer_type _killer)1695 void cloud_struct::set_killer(killer_type _killer)
1696 {
1697 killer = _killer;
1698 whose = killer_to_whose(killer);
1699
1700 switch (killer)
1701 {
1702 case KILL_YOU:
1703 killer = KILL_YOU_MISSILE;
1704 break;
1705
1706 case KILL_MON:
1707 killer = KILL_MON_MISSILE;
1708 break;
1709
1710 default:
1711 break;
1712 }
1713 }
1714
agent() const1715 actor *cloud_struct::agent() const
1716 {
1717 return find_agent(source, whose);
1718 }
1719
cloud_name(bool terse) const1720 string cloud_struct::cloud_name(bool terse) const
1721 {
1722 return cloud_type_name(type, terse);
1723 }
1724
announce_actor_engulfed(const actor * act,bool beneficial) const1725 void cloud_struct::announce_actor_engulfed(const actor *act,
1726 bool beneficial) const
1727 {
1728 ASSERT(act); // XXX: change to const actor &act
1729 if (!you.can_see(*act))
1730 return;
1731
1732 // Normal clouds. (Unmodified rain clouds have a different message.)
1733 if (type != CLOUD_RAIN && type != CLOUD_STORM)
1734 {
1735 mprf("%s %s in %s.",
1736 act->name(DESC_THE).c_str(),
1737 beneficial ? act->conj_verb("bask").c_str()
1738 : (act->conj_verb("are") + " engulfed").c_str(),
1739 cloud_name().c_str());
1740 return;
1741 }
1742
1743 // Don't produce monster-in-rain messages in the interests
1744 // of spam reduction.
1745 if (act->is_player())
1746 {
1747 mprf("%s %s standing in %s.",
1748 act->name(DESC_THE).c_str(),
1749 act->conj_verb("are").c_str(),
1750 type == CLOUD_STORM ? "a thunderstorm" : "the rain");
1751 }
1752 }
1753
1754 /**
1755 * What colour is the given cloud?
1756 *
1757 * @param cloudno The cloud in question.
1758 * @return An appropriate colour for the cloud.
1759 * May vary from call to call (randomized for some cloud
1760 * types).
1761 */
get_cloud_colour(const cloud_struct & cloud)1762 colour_t get_cloud_colour(const cloud_struct &cloud)
1763 {
1764 // if we have the colour in data, use that.
1765 if (clouds[cloud.type].colour)
1766 return clouds[cloud.type].colour;
1767
1768 // weird clouds
1769 switch (cloud.type)
1770 {
1771 case CLOUD_FIRE:
1772 case CLOUD_FOREST_FIRE:
1773 if (cloud.decay <= 20)
1774 return RED;
1775 if (cloud.decay <= 40)
1776 return LIGHTRED;
1777
1778 // total weight 16
1779 return random_choose_weighted(9, YELLOW,
1780 4, RED,
1781 3, LIGHTRED);
1782
1783 case CLOUD_COLD:
1784 if (cloud.decay <= 20)
1785 return BLUE;
1786 if (cloud.decay <= 40)
1787 return LIGHTBLUE;
1788
1789 // total weight 16
1790 return random_choose_weighted(9, WHITE,
1791 4, BLUE,
1792 3, LIGHTBLUE);
1793 break;
1794
1795 default:
1796 return LIGHTGREY;
1797 }
1798 }
1799
get_cloud_originator(const coord_def & pos)1800 coord_def get_cloud_originator(const coord_def& pos)
1801 {
1802 const cloud_struct* cloud = cloud_at(pos);
1803 if (!cloud)
1804 return coord_def();
1805 const actor *agent = actor_by_mid(cloud->source);
1806 if (!agent)
1807 return coord_def();
1808 return agent->pos();
1809 }
1810
remove_vortex_clouds(mid_t whose)1811 void remove_vortex_clouds(mid_t whose)
1812 {
1813 // Needed to clean up after the end of tornado cooldown, so we can again
1814 // assume all "raging winds" clouds are harmful. This is needed only
1815 // because map_knowledge doesn't preserve the knowledge about whom the
1816 // cloud belongs to. If this changes, please remove this function. For
1817 // example, this approach doesn't work if we ever make Tornado a monster
1818 // spell (excluding immobile and mindless casters).
1819 // XXX: this comment seems impossibly out of date? ^
1820
1821 // We can't iterate over env.cloud directly because delete_cloud
1822 // will remove this cloud and invalidate our iterator.
1823 vector<coord_def> vortices;
1824 for (auto& entry : env.cloud)
1825 if (entry.second.type == CLOUD_VORTEX && entry.second.source == whose)
1826 vortices.push_back(entry.first);
1827
1828 for (auto pos : vortices)
1829 delete_cloud(pos);
1830 }
1831
_spread_cloud(coord_def pos,cloud_type type,int radius,int pow,int & remaining,int ratio=10,mid_t agent_mid=0,kill_category kcat=KC_OTHER)1832 static void _spread_cloud(coord_def pos, cloud_type type, int radius, int pow,
1833 int &remaining, int ratio = 10,
1834 mid_t agent_mid = 0, kill_category kcat = KC_OTHER)
1835 {
1836 bolt beam;
1837 beam.target = pos;
1838 beam.use_target_as_pos = true;
1839 explosion_map exp_map;
1840 exp_map.init(INT_MAX);
1841 beam.determine_affected_cells(exp_map, coord_def(), 0,
1842 radius, true, true);
1843
1844 coord_def centre(9,9);
1845 for (distance_iterator di(pos, true, false); di; ++di)
1846 {
1847 if (di.radius() > radius)
1848 return;
1849
1850 if ((exp_map(*di - pos + centre) < INT_MAX) && !cloud_at(*di)
1851 && (di.radius() < radius || x_chance_in_y(ratio, 100)))
1852 {
1853 place_cloud(type, *di, pow + random2(pow), nullptr);
1854 --remaining;
1855
1856 // Setting this way since the agent of the cloud may be dead before
1857 // cloud is placed, so no agent exists to pass to place_cloud (though
1858 // proper blame should still be assigned)
1859 if (cloud_struct* cloud = cloud_at(*di))
1860 {
1861 cloud->source = agent_mid;
1862 cloud->whose = kcat;
1863 }
1864 }
1865
1866 // Placed all clouds for this spreader
1867 if (remaining == 0)
1868 return;
1869 }
1870 }
1871
run_cloud_spreaders(int dur)1872 void run_cloud_spreaders(int dur)
1873 {
1874 if (!dur)
1875 return;
1876
1877 for (map_marker *marker : env.markers.get_all(MAT_CLOUD_SPREADER))
1878 {
1879 map_cloud_spreader_marker * const mark
1880 = dynamic_cast<map_cloud_spreader_marker*>(marker);
1881
1882 mark->speed_increment += dur;
1883 int rad = min(mark->speed_increment / mark->speed, mark->max_rad - 1) + 1;
1884 int ratio = (mark->speed_increment - ((rad - 1) * mark->speed))
1885 * 100 / mark->speed;
1886
1887 if (ratio == 0)
1888 {
1889 rad--;
1890 ratio = 100;
1891 }
1892
1893 _spread_cloud(mark->pos, mark->ctype, rad, mark->duration,
1894 mark->remaining, ratio, mark->agent_mid, mark->kcat);
1895 if ((rad >= mark->max_rad && ratio >= 100) || mark->remaining == 0)
1896 {
1897 env.markers.remove(mark);
1898 break;
1899 }
1900 }
1901 }
1902
cloud_type_tile_info(cloud_type type)1903 const cloud_tile_info& cloud_type_tile_info(cloud_type type)
1904 {
1905 return clouds[type].tile_info;
1906 }
1907
1908 /// Knock out clouds & set the still winds level flag; also message.
start_still_winds()1909 void start_still_winds()
1910 {
1911 delete_all_clouds();
1912 env.level_state |= LSTATE_STILL_WINDS;
1913 mprf(MSGCH_WARN, "%s", "The air becomes perfectly still.");
1914 }
1915
end_still_winds()1916 void end_still_winds()
1917 {
1918 env.level_state &= ~LSTATE_STILL_WINDS;
1919 mpr("The air resumes its normal movements.");
1920 }
1921
1922 /**
1923 * Surround a monster with clouds of a certain type (excepting squares with an
1924 * allied monster). This also deletes the same cloud if it's on top of the
1925 * monster (which will happen as they walk around).
1926 */
surround_actor_with_cloud(const actor * a,cloud_type cloud)1927 void surround_actor_with_cloud(const actor* a, cloud_type cloud)
1928 {
1929 const coord_def pos = a->pos();
1930 const cloud_struct* overhead = cloud_at(pos);
1931 if (overhead && overhead->type == cloud)
1932 delete_cloud(pos);
1933 for (adjacent_iterator ai(pos); ai; ++ai)
1934 {
1935 const cloud_struct* existing = cloud_at(*ai);
1936 // dprf("surround_actor_with_cloud x:%d y:%d solid:%d cloud_at:%s",
1937 // ai->x, ai->y, cell_is_solid(*ai), existing ? "y" : "n");
1938 if (cell_is_solid(*ai))
1939 continue;
1940 if (existing && existing->type != cloud)
1941 continue;
1942 const monster* mons = monster_at(*ai);
1943 if (mons && mons->alive() && mons_aligned(a, mons))
1944 continue;
1945 place_cloud(cloud, *ai, 2 + random2(6), a);
1946 }
1947 }
1948