1 /*
2 * @file
3 * @brief Monster explosion functionality.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "mon-explode.h"
9
10 #include "beam.h"
11 #include "cloud.h"
12 #include "coordit.h"
13 #include "dungeon-char-type.h"
14 #include "english.h"
15 #include "env.h"
16 #include "fineff.h"
17 #include "fprop.h"
18 #include "message.h"
19 #include "monster.h"
20 #include "mon-death.h" // YOU_KILL
21 #include "mon-place.h"
22 #include "mon-util.h"
23 #include "mpr.h"
24 #include "spl-goditem.h"
25 #include "state.h"
26 #include "stringutil.h"
27 #include "target.h"
28 #include "terrain.h"
29 #include "torment-source-type.h"
30 #include "view.h"
31 #include "viewchar.h"
32
_setup_base_explosion(bolt & beam,const monster & origin)33 static void _setup_base_explosion(bolt & beam, const monster& origin)
34 {
35 beam.is_tracer = false;
36 beam.is_explosion = true;
37 beam.is_death_effect = true;
38 beam.source_id = origin.mid;
39 beam.glyph = dchar_glyph(DCHAR_FIRED_BURST);
40 beam.source = origin.pos();
41 beam.source_name = origin.base_name(DESC_BASENAME, true);
42 beam.target = origin.pos();
43 beam.explode_noise_msg = "You hear an explosion!";
44
45 if (!crawl_state.game_is_arena() && origin.attitude == ATT_FRIENDLY
46 && !origin.is_summoned())
47 {
48 beam.thrower = KILL_YOU;
49 }
50 else
51 beam.thrower = KILL_MON;
52
53 beam.aux_source.clear();
54 beam.attitude = origin.attitude;
55 }
56
setup_spore_explosion(bolt & beam,const monster & origin)57 void setup_spore_explosion(bolt & beam, const monster& origin)
58 {
59 _setup_base_explosion(beam, origin);
60 beam.flavour = BEAM_SPORE;
61 beam.damage = dice_def(3, 5 + origin.get_hit_dice());
62 beam.name = "explosion of spores";
63 beam.colour = LIGHTGREY;
64 beam.ex_size = 1;
65 }
66
ball_lightning_damage(int hd)67 dice_def ball_lightning_damage(int hd)
68 {
69 return dice_def(3, 5 + hd * 5 / 4);
70 }
71
_setup_lightning_explosion(bolt & beam,const monster & origin)72 static void _setup_lightning_explosion(bolt & beam, const monster& origin)
73 {
74 _setup_base_explosion(beam, origin);
75 beam.flavour = BEAM_ELECTRICITY;
76 beam.damage = ball_lightning_damage(origin.get_hit_dice());
77 beam.name = "blast of lightning";
78 beam.explode_noise_msg = "You hear a clap of thunder!";
79 beam.colour = LIGHTCYAN;
80 beam.ex_size = x_chance_in_y(origin.get_hit_dice(), 24) ? 3 : 2;
81 if (origin.summoner)
82 beam.origin_spell = SPELL_CONJURE_BALL_LIGHTNING;
83 // Don't credit the player for ally-summoned ball lightning explosions.
84 if (origin.summoner && origin.summoner != MID_PLAYER)
85 beam.thrower = KILL_MON;
86 }
87
prism_damage(int hd,bool fully_powered)88 dice_def prism_damage(int hd, bool fully_powered)
89 {
90 const int dice = fully_powered ? 3 : 2;
91 return dice_def(dice, 6 + hd * 7 / 4);
92 }
93
_setup_prism_explosion(bolt & beam,const monster & origin)94 static void _setup_prism_explosion(bolt& beam, const monster& origin)
95 {
96 _setup_base_explosion(beam, origin);
97 beam.flavour = BEAM_MMISSILE;
98 beam.damage = prism_damage(origin.get_hit_dice(), origin.prism_charge == 2);
99 beam.name = "blast of energy";
100 beam.colour = MAGENTA;
101 beam.ex_size = origin.prism_charge;
102 if (origin.summoner)
103 beam.origin_spell = SPELL_FULMINANT_PRISM;
104 dprf("prism hd: %d, damage: %dd%d", origin.get_hit_dice(),
105 beam.damage.num, beam.damage.size);
106 }
107
_setup_bennu_explosion(bolt & beam,const monster & origin)108 static void _setup_bennu_explosion(bolt& beam, const monster& origin)
109 {
110 _setup_base_explosion(beam, origin);
111 beam.flavour = BEAM_NEG;
112 beam.damage = dice_def(3, 5 + origin.get_hit_dice() * 5 / 4);
113 beam.name = "pyre of ghostly fire";
114 beam.explode_noise_msg = "You hear an otherworldly crackling!";
115 beam.colour = CYAN;
116 beam.ex_size = 2;
117 }
118
_setup_inner_flame_explosion(bolt & beam,const monster & origin,actor * agent)119 static void _setup_inner_flame_explosion(bolt & beam, const monster& origin,
120 actor* agent)
121 {
122 _setup_base_explosion(beam, origin);
123 const int size = origin.body_size(PSIZE_BODY);
124 beam.flavour = BEAM_FIRE;
125 beam.damage = (size > SIZE_BIG) ? dice_def(3, 25) :
126 (size > SIZE_TINY) ? dice_def(3, 20) :
127 dice_def(3, 15);
128 beam.name = "fiery explosion";
129 beam.colour = RED;
130 beam.ex_size = (size > SIZE_BIG) ? 2 : 1;
131 beam.source_name = origin.name(DESC_PLAIN, true);
132 beam.origin_spell = SPELL_INNER_FLAME;
133 beam.thrower = (agent && agent->is_player()) ? KILL_YOU_MISSILE
134 : KILL_MON_MISSILE;
135 if (agent)
136 beam.source_id = agent->mid;
137 }
138
_setup_bloated_husk_explosion(bolt & beam,const monster & origin)139 static void _setup_bloated_husk_explosion(bolt & beam, const monster& origin)
140 {
141 _setup_base_explosion(beam, origin);
142 beam.flavour = BEAM_MMISSILE;
143 beam.damage = dice_def(8, origin.get_hit_dice());
144 beam.name = "blast of putrescent gases";
145 beam.explode_noise_msg = "You hear an high-pitched explosion!";
146 beam.colour = GREEN;
147 beam.ex_size = 2;
148
149 }
150
151 struct monster_explosion {
152 function<void(bolt&, const monster&)> prep_explode;
153 string sanct_effect;
154 };
155
156 static const map<monster_type, monster_explosion> explosions {
157 { MONS_BALLISTOMYCETE_SPORE, { setup_spore_explosion } },
158 { MONS_BALL_LIGHTNING, { _setup_lightning_explosion } },
159 { MONS_LURKING_HORROR, { nullptr, "torment is averted" } },
160 { MONS_FULMINANT_PRISM, { _setup_prism_explosion } },
161 { MONS_BENNU, { _setup_bennu_explosion, "fires are quelled" } },
162 { MONS_BLOATED_HUSK, { _setup_bloated_husk_explosion } },
163 };
164
165 // When this monster dies, does it explode?
mon_explodes_on_death(monster_type mc)166 bool mon_explodes_on_death(monster_type mc)
167 {
168 return explosions.find(mc) != explosions.end();
169 }
170
monster_explodes(const monster & mons)171 bool monster_explodes(const monster &mons)
172 {
173 if (mons.has_ench(ENCH_INNER_FLAME))
174 return true;
175 if (!mon_explodes_on_death(mons.type))
176 return false;
177 if (mons.type == MONS_FULMINANT_PRISM && mons.prism_charge <= 0)
178 return false;
179 return true;
180 }
181
explode_monster(monster * mons,killer_type killer,bool pet_kill,bool wizard)182 bool explode_monster(monster* mons, killer_type killer,
183 bool pet_kill, bool wizard)
184 {
185 if (mons->hit_points > 0 || mons->hit_points <= -15 || wizard
186 || killer == KILL_RESET || killer == KILL_DISMISSED
187 || killer == KILL_BANISHED)
188 {
189 if (killer != KILL_TIMEOUT)
190 return false;
191 }
192
193 bolt beam;
194 const monster_type type = mons->type;
195 string sanct_msg = "";
196 string boom_msg = make_stringf("%s explodes!", mons->full_name(DESC_THE).c_str());
197 actor* agent = nullptr;
198 bool inner_flame = false;
199
200 auto it = explosions.find(type);
201 if (it != explosions.end())
202 {
203 const monster_explosion &explosion = it->second;
204 if (explosion.prep_explode)
205 explosion.prep_explode(beam, *mons);
206 string effect = "explosion is contained";
207 if (explosion.sanct_effect != "")
208 effect = explosion.sanct_effect;
209 sanct_msg = string("By Zin's power, ") +
210 apostrophise(mons->name(DESC_THE)) + " " +
211 effect + ".";
212 if (type == MONS_BENNU)
213 boom_msg = make_stringf("%s blazes out!", mons->full_name(DESC_THE).c_str());
214 }
215 else
216 {
217 if (!mons->has_ench(ENCH_INNER_FLAME))
218 {
219 msg::streams(MSGCH_DIAGNOSTICS) << "Unknown spore type: "
220 << static_cast<int>(type)
221 << endl;
222 return false;
223 }
224 mon_enchant i_f = mons->get_ench(ENCH_INNER_FLAME);
225 ASSERT(i_f.ench == ENCH_INNER_FLAME);
226 agent = actor_by_mid(i_f.source);
227 _setup_inner_flame_explosion(beam, *mons, agent);
228 // This might need to change if monsters ever get the ability to cast
229 // Inner Flame...
230 if (agent && agent->is_player())
231 mons_add_blame(mons, "hexed by the player character");
232 else if (agent)
233 mons_add_blame(mons, "hexed by " + agent->name(DESC_A, true));
234 mons->flags |= MF_EXPLODE_KILL;
235 sanct_msg = "By Zin's power, the fiery explosion is contained.";
236 beam.aux_source = "ignited by their inner flame";
237 inner_flame = true;
238 }
239
240 if (beam.aux_source.empty())
241 {
242 if (type == MONS_BENNU)
243 {
244 if (YOU_KILL(killer))
245 beam.aux_source = "ignited by themself";
246 else if (pet_kill)
247 beam.aux_source = "ignited by their pet";
248 }
249 else
250 {
251 if (YOU_KILL(killer))
252 beam.aux_source = "set off by themself";
253 else if (pet_kill)
254 beam.aux_source = "set off by their pet";
255 }
256 }
257
258 if (is_sanctuary(mons->pos()))
259 {
260 if (you.can_see(*mons))
261 mprf(MSGCH_GOD, "%s", sanct_msg.c_str());
262 return false;
263 }
264
265 if (type == MONS_LURKING_HORROR)
266 torment(mons, TORMENT_LURKING_HORROR, mons->pos());
267
268 // Detach monster from the grid first, so it doesn't get hit by
269 // its own explosion. (GDL)
270 // Unless it's a phoenix, where this isn't much of a concern.
271 env.mgrid(mons->pos()) = NON_MONSTER;
272
273 // The explosion might cause a monster to be placed where the bomb
274 // used to be, so make sure that env.mgrid() doesn't get cleared a second
275 // time (causing the new monster to become floating) when
276 // mons->reset() is called.
277 if (type == MONS_BALLISTOMYCETE_SPORE)
278 mons->set_position(coord_def(0,0));
279
280 // Exploding kills the monster a bit earlier than normal.
281 mons->hit_points = -16;
282
283 // FIXME: show_more == you.see_cell(mons->pos())
284 if (type == MONS_LURKING_HORROR)
285 {
286 targeter_radius hitfunc(mons, LOS_SOLID);
287 flash_view_delay(UA_MONSTER, DARKGRAY, 300, &hitfunc);
288 }
289 else
290 explosion_fineff::schedule(beam, boom_msg, sanct_msg, inner_flame, agent);
291
292 // Monster died in explosion, so don't re-attach it to the grid.
293 return true;
294 }
295