fix #H5459 - explosions and steeds

Reported nearly five and half years ago:  mounted hero and steed
shared resistances if they got hit by an explosion.
This commit is contained in:
PatR
2022-09-25 01:21:07 -07:00
parent f4e87e8744
commit caf1eeebf9
2 changed files with 164 additions and 108 deletions

View File

@@ -4,12 +4,115 @@
#include "hack.h"
static int explosionmask(struct monst *, uchar, char);
/* Note: Arrays are column first, while the screen is row first */
static const int explosion[3][3] = {
{ S_expl_tl, S_expl_ml, S_expl_bl },
{ S_expl_tc, S_expl_mc, S_expl_bc },
{ S_expl_tr, S_expl_mr, S_expl_br } };
/* what to do at [x+i][y+j] for i=-1,0,1 and j=-1,0,1 */
enum explode_action {
EXPL_NONE = 0, /* not specified yet or no shield effect needed */
EXPL_MON = 1, /* monster is affected */
EXPL_HERO = 2, /* hero is affected */
EXPL_SKIP = 4 /* don't apply shield effect (out of bounds) */
};
/* check if shield effects are needed for location affected by explosion */
static int
explosionmask(
struct monst *m, /* target monster (might be youmonst) */
uchar adtyp, /* damage type */
char olet) /* object class (only matters for AD_DISN) */
{
int res = EXPL_NONE;
if (m == &g.youmonst) {
switch (adtyp) {
case AD_PHYS:
/* leave 'res' with EXPL_NONE */
break;
case AD_MAGM:
if (Antimagic)
res = EXPL_HERO;
break;
case AD_FIRE:
if (Fire_resistance)
res = EXPL_HERO;
break;
case AD_COLD:
if (Cold_resistance)
res = EXPL_HERO;
break;
case AD_DISN:
if ((olet == WAND_CLASS)
? (nonliving(m->data) || is_demon(m->data))
: Disint_resistance)
res = EXPL_HERO;
break;
case AD_ELEC:
if (Shock_resistance)
res = EXPL_HERO;
break;
case AD_DRST:
if (Poison_resistance)
res = EXPL_HERO;
break;
case AD_ACID:
if (Acid_resistance)
res = EXPL_HERO;
break;
default:
impossible("explosion type %d?", adtyp);
break;
}
} else {
/* 'm' is a monster */
switch (adtyp) {
case AD_PHYS:
break;
case AD_MAGM:
if (resists_magm(m))
res = EXPL_MON;
break;
case AD_FIRE:
if (resists_fire(m))
res = EXPL_MON;
break;
case AD_COLD:
if (resists_cold(m))
res = EXPL_MON;
break;
case AD_DISN:
if ((olet == WAND_CLASS)
? (nonliving(m->data) || is_demon(m->data)
|| is_vampshifter(m))
: !!resists_disint(m))
res = EXPL_MON;
break;
case AD_ELEC:
if (resists_elec(m))
res = EXPL_MON;
break;
case AD_DRST:
if (resists_poison(m))
res = EXPL_MON;
break;
case AD_ACID:
if (resists_acid(m))
res = EXPL_MON;
break;
default:
impossible("explosion type %d?", adtyp);
break;
}
}
return res;
}
/* Note: I had to choose one of three possible kinds of "type" when writing
* this function: a wand type (like in zap.c), an adtyp, or an object type.
* Wand types get complex because they must be converted to adtyps for
@@ -29,7 +132,8 @@ static const int explosion[3][3] = {
*/
void
explode(
coordxy x, coordxy y, /* explosion's location; adjacent spots are also affected */
coordxy x, coordxy y, /* explosion's location;
* adjacent spots are also affected */
int type, /* same as in zap.c; -(wand typ) for some WAND_CLASS */
int dam, /* damage amount */
char olet, /* object class or BURNING_OIL or MON_EXPLODE */
@@ -44,7 +148,8 @@ explode(
struct monst *mtmp, *mdef = 0;
uchar adtyp;
int explmask[3][3]; /* 0=normal explosion, 1=do shieldeff, 2=do nothing */
boolean shopdamage = FALSE, generic = FALSE, physical_dmg = FALSE,
coordxy xx, yy;
boolean shopdamage = FALSE, generic = FALSE,
do_hallu = FALSE, inside_engulfer, grabbed, grabbing;
coord grabxy;
char hallu_buf[BUFSZ], killr_buf[BUFSZ];
@@ -183,95 +288,34 @@ explode(
any_shield = visible = FALSE;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++) {
if (!isok(i + x - 1, j + y - 1)) {
explmask[i][j] = 2;
xx = x + i - 1;
yy = y + j - 1;
if (!isok(xx, yy)) {
explmask[i][j] = EXPL_SKIP;
continue;
} else
explmask[i][j] = 0;
}
explmask[i][j] = EXPL_NONE;
if (u_at(i + x - 1, j + y - 1)) {
switch (adtyp) {
case AD_PHYS:
explmask[i][j] = 0;
break;
case AD_MAGM:
explmask[i][j] = !!Antimagic;
break;
case AD_FIRE:
explmask[i][j] = !!Fire_resistance;
break;
case AD_COLD:
explmask[i][j] = !!Cold_resistance;
break;
case AD_DISN:
explmask[i][j] = (olet == WAND_CLASS)
? !!(nonliving(g.youmonst.data)
|| is_demon(g.youmonst.data))
: !!Disint_resistance;
break;
case AD_ELEC:
explmask[i][j] = !!Shock_resistance;
break;
case AD_DRST:
explmask[i][j] = !!Poison_resistance;
break;
case AD_ACID:
explmask[i][j] = !!Acid_resistance;
physical_dmg = TRUE;
break;
default:
impossible("explosion type %d?", adtyp);
break;
}
if (u_at(xx, yy)) {
explmask[i][j] = explosionmask(&g.youmonst, adtyp, olet);
}
/* can be both you and mtmp if you're swallowed or riding */
mtmp = m_at(i + x - 1, j + y - 1);
if (!mtmp && u_at(i + x - 1, j + y - 1))
mtmp = m_at(xx, yy);
if (!mtmp && u_at(xx, yy))
mtmp = u.usteed;
if (mtmp && DEADMONSTER(mtmp))
mtmp = 0;
if (mtmp) {
if (DEADMONSTER(mtmp))
explmask[i][j] = 2;
else
switch (adtyp) {
case AD_PHYS:
break;
case AD_MAGM:
explmask[i][j] |= resists_magm(mtmp);
break;
case AD_FIRE:
explmask[i][j] |= resists_fire(mtmp);
break;
case AD_COLD:
explmask[i][j] |= resists_cold(mtmp);
break;
case AD_DISN:
explmask[i][j] |= (olet == WAND_CLASS)
? (nonliving(mtmp->data)
|| is_demon(mtmp->data)
|| is_vampshifter(mtmp))
: resists_disint(mtmp);
break;
case AD_ELEC:
explmask[i][j] |= resists_elec(mtmp);
break;
case AD_DRST:
explmask[i][j] |= resists_poison(mtmp);
break;
case AD_ACID:
explmask[i][j] |= resists_acid(mtmp);
break;
default:
impossible("explosion type %d?", adtyp);
break;
}
explmask[i][j] |= explosionmask(mtmp, adtyp, olet);
}
if (mtmp && cansee(i + x - 1, j + y - 1) && !canspotmon(mtmp))
map_invisible(i + x - 1, j + y - 1);
if (mtmp && cansee(xx, yy) && !canspotmon(mtmp))
map_invisible(xx, yy);
else if (!mtmp)
(void) unmap_invisible(i + x - 1, j + y - 1);
if (cansee(i + x - 1, j + y - 1))
(void) unmap_invisible(xx, yy);
if (cansee(xx, yy))
visible = TRUE;
if (explmask[i][j] == 1)
if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
any_shield = TRUE;
}
@@ -279,11 +323,13 @@ explode(
/* Start the explosion */
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++) {
if (explmask[i][j] == 2)
if (explmask[i][j] == EXPL_SKIP)
continue;
xx = x + i - 1;
yy = y + j - 1;
tmp_at(starting ? DISP_BEAM : DISP_CHANGE,
explosion_to_glyph(expltype, explosion[i][j]));
tmp_at(i + x - 1, j + y - 1);
tmp_at(xx, yy);
starting = 0;
}
curs_on_u(); /* will flush screen and output */
@@ -292,13 +338,15 @@ explode(
for (k = 0; k < SHIELD_COUNT; k++) {
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++) {
if (explmask[i][j] == 1)
xx = x + i - 1;
yy = y + j - 1;
if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
/*
* Bypass tmp_at() and send the shield glyphs
* directly to the buffered screen. tmp_at()
* will clean up the location for us later.
*/
show_glyph(i + x - 1, j + y - 1,
show_glyph(xx, yy,
cmap_to_glyph(shield_static[k]));
}
curs_on_u(); /* will flush screen and output */
@@ -308,10 +356,12 @@ explode(
/* Cover last shield glyph with blast symbol. */
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++) {
if (explmask[i][j] == 1)
show_glyph(
i + x - 1, j + y - 1,
explosion_to_glyph(expltype, explosion[i][j]));
xx = x + i - 1;
yy = y + j - 1;
if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
show_glyph(xx, yy,
explosion_to_glyph(expltype,
explosion[i][j]));
}
} else { /* delay a little bit. */
@@ -334,37 +384,42 @@ explode(
if (!Deaf && !didmsg)
pline("Boom!");
/* apply effects to monsters and floor objects first, in case the
damage to the hero is fatal and leaves bones */
if (dam) {
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
if (explmask[i][j] == 2)
if (explmask[i][j] == EXPL_SKIP)
continue;
if (u_at(i + x - 1, j + y - 1)) {
uhurt = (explmask[i][j] == 1) ? 1 : 2;
xx = x + i - 1;
yy = y + j - 1;
if (u_at(xx, yy)) {
uhurt = ((explmask[i][j] & EXPL_HERO) != 0) ? 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)
} else if (inside_engulfer) {
/* for inside_engulfer, only <u.ux,u.uy> is affected */
continue;
}
idamres = idamnonres = 0;
/* Affect the floor unless the player caused the explosion from
* inside their engulfer. */
/* Affect the floor unless the player caused the explosion
* from inside their engulfer. */
if (!(u.uswallow && !g.context.mon_moving))
(void) zap_over_floor((coordxy) (i + x - 1),
(coordxy) (j + y - 1), type,
(void) zap_over_floor(xx, yy, type,
&shopdamage, exploding_wand_typ);
mtmp = m_at(i + x - 1, j + y - 1);
if (!mtmp && u_at(i + x - 1, j + y - 1))
mtmp = m_at(xx, yy);
if (!mtmp && u_at(xx, yy))
mtmp = u.usteed;
if (!mtmp)
continue;
if (do_hallu) {
int tryct = 0;
/* replace "gas spore" with a different description
for each target (we can't distinguish personal names
like "Barney" here in order to suppress "the" below,
@@ -372,7 +427,7 @@ explode(
do {
Sprintf(hallu_buf, "%s explosion",
s_suffix(rndmonnam((char *) 0)));
} while (*hallu_buf != lowc(*hallu_buf));
} while (*hallu_buf != lowc(*hallu_buf) && ++tryct < 20);
str = hallu_buf;
}
if (engulfing_u(mtmp)) {
@@ -435,7 +490,7 @@ explode(
}
pline("%s gets slightly %s!", Monnam(u.ustuck), adj);
}
} else if (cansee(i + x - 1, j + y - 1)) {
} else if (cansee(xx, yy)) {
if (mtmp->m_ap_type)
seemimic(mtmp);
pline("%s is caught in the %s!", Monnam(mtmp), str);
@@ -451,7 +506,7 @@ explode(
idamnonres += destroy_mitem(mtmp, RING_CLASS, (int) adtyp);
idamnonres += destroy_mitem(mtmp, WAND_CLASS, (int) adtyp);
if (explmask[i][j] == 1) {
if ((explmask[i][j] & EXPL_MON) != 0) {
golemeffects(mtmp, (int) adtyp, dam + idamres);
mtmp->mhp -= idamnonres;
} else {
@@ -462,8 +517,8 @@ explode(
int mdam = dam;
if (resist(mtmp, olet, 0, FALSE)) {
/* inside_engulfer: <i+x-1,j+y-1> == <u.ux,u.uy> */
if (cansee(i + x - 1, j + y - 1) || inside_engulfer)
/* inside_engulfer: <xx,yy> == <u.ux,u.uy> */
if (cansee(xx, yy) || inside_engulfer)
pline("%s resists the %s!", Monnam(mtmp), str);
mdam = (dam + 1) / 2;
}
@@ -539,7 +594,7 @@ explode(
if (Invulnerable) {
damu = 0;
You("are unharmed!");
} else if (adtyp == AD_PHYS || physical_dmg)
} else if (adtyp == AD_PHYS || adtyp == AD_ACID)
damu = Maybe_Half_Phys(damu);
if (adtyp == AD_FIRE) {
(void) burnarmor(&g.youmonst);