Skip to content

Commit

Permalink
Exploding spheres cause real explosions
Browse files Browse the repository at this point in the history
Despite active explosion attacks being called explosions in-game,
they only affected a single target, and were handled differently
from actual explosions. Make them do an actual explosion instead.
This should make spheres more interesting and inspire different
tactics handling them.

Because spheres deal more damage on average and can destroy items
in their explosions, their difficulty has been increased slightly.

Polyselfed hero exploding won't cause elemental damage to their
own gear.

Originally from xNetHack by copperwater <aosdict@gmail.com>.
  • Loading branch information
paxed committed May 22, 2021
1 parent 7f8cfb4 commit 6b60618
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 127 deletions.
1 change: 1 addition & 0 deletions doc/fixes37.0
Expand Up @@ -530,6 +530,7 @@ boost hit points of some golems
make anti-magic fields drain more energy and prevent them from showing up
too early in the dungeon
eating magical monsters such as wizards or shamans may give a mild buzz
make exploding spheres create an actual explosion


Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
Expand Down
3 changes: 3 additions & 0 deletions include/extern.h
Expand Up @@ -735,6 +735,8 @@ extern void explode(int, int, int, int, char, int);
extern long scatter(int, int, int, unsigned int, struct obj *);
extern void splatter_burning_oil(int, int, boolean);
extern void explode_oil(struct obj *, int, int);
extern int adtyp_to_expltype(int);
extern void mon_explodes(struct monst *, struct attack *);

/* ### extralev.c ### */

Expand Down Expand Up @@ -2740,6 +2742,7 @@ extern boolean do_stone_u(struct monst *);
extern void do_stone_mon(struct monst *, struct attack *, struct monst *,
struct mhitm_data *);
extern int damageum(struct monst *, struct attack *, int);
extern int explum(struct monst *, struct attack *);
extern void missum(struct monst *, struct attack *, boolean);
extern int passive(struct monst *, struct obj *, boolean, boolean, uchar,
boolean);
Expand Down
7 changes: 7 additions & 0 deletions include/hack.h
Expand Up @@ -527,6 +527,13 @@ enum getobj_callback_returns {
GETOBJ_SUGGEST = 2,
};

/* constant passed to explode() for gas spores because gas spores are weird
* Specifically, this is an exception to the whole "explode() uses dobuzz types"
* system (the range -1 to -9 isn't used by it, for some reason), where this is
* effectively an extra dobuzz type, and some zap.c code needs to be aware of
* it. */
#define PHYS_EXPL_TYPE -1

/*
* option setting restrictions
*/
Expand Down
131 changes: 118 additions & 13 deletions src/explode.c
Expand Up @@ -17,6 +17,8 @@ static const int explosion[3][3] = { { S_explode1, S_explode4, S_explode7 },
* did it, and with a wand, spell, or breath weapon? Object types share both
* these disadvantages....
*
* Note: anything with a AT_BOOM AD_PHYS attack uses PHYS_EXPL_TYPE for type.
*
* Important note about Half_physical_damage:
* Unlike losehp(), explode() makes the Half_physical_damage adjustments
* itself, so the caller should never have done that ahead of time.
Expand Down Expand Up @@ -46,6 +48,7 @@ explode(
coord grabxy;
char hallu_buf[BUFSZ], killr_buf[BUFSZ];
short exploding_wand_typ = 0;
boolean you_exploding = (olet == MON_EXPLODE && type >= 0);

if (olet == WAND_CLASS) { /* retributive strike */
/* 'type' is passed as (wand's object type * -1); save
Expand Down Expand Up @@ -113,51 +116,61 @@ explode(
* skip harm to gear of any extended targets when inflicting damage.
*/

if (olet == MON_EXPLODE) {
if (olet == MON_EXPLODE && !you_exploding) {
/* when explode() is called recursively, g.killer.name might change so
we need to retain a copy of the current value for this explosion */
str = strcpy(killr_buf, g.killer.name);
do_hallu = (Hallucination
&& (strstri(str, "'s explosion")
|| strstri(str, "s' explosion")));
}
if (type == PHYS_EXPL_TYPE) {
/* currently only gas spores */
adtyp = AD_PHYS;
} else
} else {
/* If str is e.g. "flaming sphere's explosion" from above, we want to
* still assign adtyp appropriately, but not replace str. */
const char *adstr = NULL;

switch (abs(type) % 10) {
case 0:
str = "magical blast";
adstr = "magical blast";
adtyp = AD_MAGM;
break;
case 1:
str = (olet == BURNING_OIL) ? "burning oil"
adstr = (olet == BURNING_OIL) ? "burning oil"
: (olet == SCROLL_CLASS) ? "tower of flame" : "fireball";
/* fire damage, not physical damage */
adtyp = AD_FIRE;
break;
case 2:
str = "ball of cold";
adstr = "ball of cold";
adtyp = AD_COLD;
break;
case 4:
str = (olet == WAND_CLASS) ? "death field"
adstr = (olet == WAND_CLASS) ? "death field"
: "disintegration field";
adtyp = AD_DISN;
break;
case 5:
str = "ball of lightning";
adstr = "ball of lightning";
adtyp = AD_ELEC;
break;
case 6:
str = "poison gas cloud";
adstr = "poison gas cloud";
adtyp = AD_DRST;
break;
case 7:
str = "splash of acid";
adstr = "splash of acid";
adtyp = AD_ACID;
break;
default:
impossible("explosion base type %d?", type);
return;
}
if (!str)
str = adstr;
}

any_shield = visible = FALSE;
for (i = 0; i < 3; i++)
Expand Down Expand Up @@ -308,18 +321,27 @@ explode(
You_hear("a blast.");
}

if (dam)
for (i = 0; i < 3; i++)
if (dam) {
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
if (explmask[i][j] == 2)
continue;
if (i + x - 1 == u.ux && j + y - 1 == u.uy)
if (i + x - 1 == u.ux && j + y - 1 == u.uy) {
uhurt = (explmask[i][j] == 1) ? 1 : 2;
/* If the player is attacking via polyself into something
* with an explosion attack, leave them (and their gear)
* unharmed, to avoid punishing them from using such
* polyforms creatively */
if (!g.context.mon_moving && you_exploding)
uhurt = 0;
}
/* for inside_engulfer, only <u.ux,u.uy> is affected */
else if (inside_engulfer)
continue;
idamres = idamnonres = 0;
if (type >= 0 && !u.uswallow)
/* Affect the floor unless the player caused the explosion from
* inside their engulfer. */
if (!(u.uswallow && !g.context.mon_moving))
(void) zap_over_floor((xchar) (i + x - 1),
(xchar) (j + y - 1), type,
&shopdamage, exploding_wand_typ);
Expand Down Expand Up @@ -479,6 +501,8 @@ explode(
setmangry(mtmp, TRUE);
}
}
}
}

/* Do your injury last */
if (uhurt) {
Expand Down Expand Up @@ -825,4 +849,85 @@ explode_oil(struct obj *obj, int x, int y)
splatter_burning_oil(x, y, diluted_oil);
}

/* Convert a damage type into an explosion display type. */
int
adtyp_to_expltype(const int adtyp)
{
switch(adtyp) {
case AD_ELEC:
/* Electricity isn't magical, but there currently isn't an electric
* explosion type. Magical is the next best thing. */
case AD_SPEL:
case AD_DREN:
case AD_ENCH:
return EXPL_MAGICAL;
case AD_FIRE:
return EXPL_FIERY;
case AD_COLD:
return EXPL_FROSTY;
case AD_DRST:
case AD_DRDX:
case AD_DRCO:
case AD_DISE:
case AD_PEST:
case AD_PHYS: /* gas spore */
return EXPL_NOXIOUS;
default:
impossible("adtyp_to_expltype: bad explosion type %d", adtyp);
return EXPL_FIERY;
}
}

/* A monster explodes in a way that produces a real explosion (e.g. a sphere or
* gas spore, not a yellow light or similar).
* This is some common code between explmu() and explmm().
*/
void
mon_explodes(struct monst *mon, struct attack *mattk)
{
int dmg;
int type;
if (mattk->damn) {
dmg = d((int) mattk->damn, (int) mattk->damd);
}
else if (mattk->damd) {
dmg = d((int) mon->data->mlevel + 1, (int) mattk->damd);
}
else {
dmg = 0;
}

if (mattk->adtyp == AD_PHYS) {
type = PHYS_EXPL_TYPE;
}
else if (mattk->adtyp >= AD_MAGM && mattk->adtyp <= AD_SPC2) {
/* The -1, +20, *-1 math is to set it up as a 'monster breath' type for
* the explosions (it isn't, but this is the closest analogue). */
type = -((mattk->adtyp - 1) + 20);
}
else {
impossible("unknown type for mon_explode %d", mattk->adtyp);
return;
}

/* Kill it now so it won't appear to be caught in its own explosion.
* Must check to see if already dead - which happens if this is called from
* an AT_BOOM attack upon death. */
if (!DEADMONSTER(mon)) {
mondead(mon);
}

/* This might end up killing you, too; you never know...
* also, it is used in explode() messages */
Sprintf(g.killer.name, "%s explosion",
s_suffix(pmname(mon->data, Mgender(mon))));
g.killer.format = KILLED_BY_AN;

explode(mon->mx, mon->my, type, dmg, MON_EXPLODE,
adtyp_to_expltype(mattk->adtyp));

/* reset killer */
g.killer.name[0] = '\0';
}

/*explode.c*/
3 changes: 3 additions & 0 deletions src/hack.c
Expand Up @@ -1753,8 +1753,11 @@ domove_core(void)

nomul(0);
if (explo) {
struct attack *attk;
/* no monster has been attacked so we have bypassed explum() */
wake_nearto(u.ux, u.uy, 7 * 7); /* same radius as explum() */
if ((attk = attacktype_fordmg(g.youmonst.data, AT_EXPL, AD_ANY)))
explum((struct monst *) 0, attk);
u.mh = -1; /* dead in the current form */
rehumanize();
}
Expand Down
10 changes: 9 additions & 1 deletion src/mhitm.c
Expand Up @@ -827,7 +827,15 @@ explmm(struct monst *magr, struct monst *mdef, struct attack *mattk)
else
noises(magr, mattk);

result = mdamagem(magr, mdef, mattk, (struct obj *) 0, 0);
/* monster explosion types which actually create an explosion */
if (mattk->adtyp == AD_FIRE || mattk->adtyp == AD_COLD
|| mattk->adtyp == AD_ELEC) {
mon_explodes(magr, mattk);
/* unconditionally set AGR_DIED here; lifesaving is accounted below */
result = MM_AGR_DIED | (DEADMONSTER(mdef) ? MM_DEF_DIED : 0);
} else {
result = mdamagem(magr, mdef, mattk, (struct obj *) 0, 0);
}

/* Kill off aggressor if it didn't die. */
if (!(result & MM_AGR_DIED)) {
Expand Down

0 comments on commit 6b60618

Please sign in to comment.