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