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
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 ### */
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);
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
*/
* 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.
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
* 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++)
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);
setmangry(mtmp, TRUE);
}
}
+ }
+ }
/* Do your injury last */
if (uhurt) {
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*/
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();
}
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)) {
static int
explmu(struct monst *mtmp, struct attack *mattk, boolean ufound)
{
- boolean physical_damage = TRUE, kill_agr = TRUE;
+ boolean kill_agr = TRUE;
+ boolean not_affected;
+ int tmp;
if (mtmp->mcan)
return MM_MISS;
+ tmp = d((int) mattk->damn, (int) mattk->damd);
+ not_affected = defends((int) mattk->adtyp, uwep);
+
if (!ufound) {
pline("%s explodes at a spot in %s!",
canseemon(mtmp) ? Monnam(mtmp) : "It",
levl[mtmp->mux][mtmp->muy].typ == WATER ? "empty water"
: "thin air");
} else {
- int tmp = d((int) mattk->damn, (int) mattk->damd);
- boolean not_affected = defends((int) mattk->adtyp, uwep);
-
hitmsg(mtmp, mattk);
+ }
- switch (mattk->adtyp) {
- case AD_COLD:
- physical_damage = FALSE;
- not_affected |= Cold_resistance;
- goto common;
- case AD_FIRE:
- physical_damage = FALSE;
- not_affected |= Fire_resistance;
- goto common;
- case AD_ELEC:
- physical_damage = FALSE;
- not_affected |= Shock_resistance;
- goto common;
- case AD_PHYS:
- /* there aren't any exploding creatures with AT_EXPL attack
- for AD_PHYS damage but there might be someday; without this,
- static analysis complains that 'physical_damage' is always
- False when tested below; it's right, but having that in
- place means one less thing to update if AD_PHYS gets added */
- common:
-
- if (!not_affected) {
- if (ACURR(A_DEX) > rnd(20)) {
- You("duck some of the blast.");
- tmp = (tmp + 1) / 2;
- } else {
- if (flags.verbose)
- You("get blasted!");
- }
- if (mattk->adtyp == AD_FIRE)
- burn_away_slime();
- if (physical_damage)
- tmp = Maybe_Half_Phys(tmp);
- mdamageu(mtmp, tmp);
- } else
- monstseesu_ad(mattk->adtyp);
- break;
-
- case AD_BLND:
- not_affected = resists_blnd(&g.youmonst);
- if (!not_affected) {
- /* sometimes you're affected even if it's invisible */
- if (mon_visible(mtmp) || (rnd(tmp /= 2) > u.ulevel)) {
- You("are blinded by a blast of light!");
- make_blinded((long) tmp, FALSE);
- if (!Blind)
- Your1(vision_clears);
- } else if (flags.verbose)
- You("get the impression it was not terribly bright.");
- }
- break;
-
- case AD_HALU:
- not_affected |= Blind || (u.umonnum == PM_BLACK_LIGHT
- || u.umonnum == PM_VIOLET_FUNGUS
- || dmgtype(g.youmonst.data, AD_STUN));
- if (!not_affected) {
- boolean chg;
- if (!Hallucination)
- You("are caught in a blast of kaleidoscopic light!");
- /* avoid hallucinating the black light as it dies */
- mondead(mtmp); /* remove it from map now */
- kill_agr = FALSE; /* already killed (maybe lifesaved) */
- chg =
- make_hallucinated(HHallucination + (long) tmp, FALSE, 0L);
- You("%s.", chg ? "are freaked out" : "seem unaffected");
- }
- break;
-
- default:
- break;
+ switch (mattk->adtyp) {
+ case AD_COLD:
+ case AD_FIRE:
+ case AD_ELEC:
+ mon_explodes(mtmp, mattk);
+ if (!DEADMONSTER(mtmp))
+ kill_agr = FALSE; /* lifesaving? */
+ break;
+ case AD_BLND:
+ not_affected = resists_blnd(&g.youmonst);
+ if (ufound && !not_affected) {
+ /* sometimes you're affected even if it's invisible */
+ if (mon_visible(mtmp) || (rnd(tmp /= 2) > u.ulevel)) {
+ You("are blinded by a blast of light!");
+ make_blinded((long) tmp, FALSE);
+ if (!Blind)
+ Your1(vision_clears);
+ } else if (flags.verbose)
+ You("get the impression it was not terribly bright.");
}
- if (not_affected) {
- You("seem unaffected by it.");
- ugolemeffects((int) mattk->adtyp, tmp);
+ break;
+ case AD_HALU:
+ not_affected |= Blind || (u.umonnum == PM_BLACK_LIGHT
+ || u.umonnum == PM_VIOLET_FUNGUS
+ || dmgtype(g.youmonst.data, AD_STUN));
+ if (ufound && !not_affected) {
+ boolean chg;
+ if (!Hallucination)
+ You("are caught in a blast of kaleidoscopic light!");
+ /* avoid hallucinating the black light as it dies */
+ mondead(mtmp); /* remove it from map now */
+ kill_agr = FALSE; /* already killed (maybe lifesaved) */
+ chg =
+ make_hallucinated(HHallucination + (long) tmp, FALSE, 0L);
+ You("%s.", chg ? "are freaked out" : "seem unaffected");
}
+ break;
+ default:
+ impossible("unknown exploder damage type %d", mattk->adtyp);
+ break;
+ }
+ if (not_affected) {
+ You("seem unaffected by it.");
+ ugolemeffects((int) mattk->adtyp, tmp);
}
- if (kill_agr)
+ if (kill_agr && !DEADMONSTER(mtmp))
mondead(mtmp);
wake_nearto(mtmp->mx, mtmp->my, 7 * 7);
return (!DEADMONSTER(mtmp)) ? MM_MISS : MM_AGR_DIED;
return FALSE;
}
- Sprintf(g.killer.name, "%s explosion",
- s_suffix(pmname(mdat, Mgender(mon))));
- g.killer.format = KILLED_BY_AN;
- explode(mon->mx, mon->my, -1, tmp, MON_EXPLODE, EXPL_NOXIOUS);
- g.killer.name[0] = '\0';
- g.killer.format = 0;
+ mon_explodes(mon, &mdat->mattk[i]);
return FALSE;
}
}
SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_COLD, MR_COLD,
M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS
| M1_NOTAKE,
- M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, CLR_WHITE),
+ M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 9, CLR_WHITE),
MON("flaming sphere", S_EYE, LVL(6, 13, 4, 0, 0),
(G_NOCORPSE | G_GENO | 2), A(ATTK(AT_EXPL, AD_FIRE, 4, 6), NO_ATTK,
NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK),
SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_FIRE, MR_FIRE,
M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS
| M1_NOTAKE,
- M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, CLR_RED),
+ M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 9, CLR_RED),
MON("shocking sphere", S_EYE, LVL(6, 13, 4, 0, 0),
(G_NOCORPSE | G_GENO | 2), A(ATTK(AT_EXPL, AD_ELEC, 4, 6), NO_ATTK,
NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK),
SIZ(10, 10, MS_SILENT, MZ_SMALL), MR_ELEC, MR_ELEC,
M1_FLY | M1_BREATHLESS | M1_NOLIMBS | M1_NOHEAD | M1_MINDLESS
| M1_NOTAKE,
- M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 8, HI_ZAP),
+ M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 10, HI_ZAP),
#if 0 /* not yet implemented */
MON("beholder", S_EYE,
LVL(6, 3, 4, 0, -10), (G_GENO | 2),
static int joust(struct monst *, struct obj *);
static void demonpet(void);
static boolean m_slips_free(struct monst *, struct attack *);
-static int explum(struct monst *, struct attack *);
static void start_engulf(struct monst *);
static void end_engulf(void);
static int gulpum(struct monst *, struct attack *);
return MM_HIT;
}
-static int
+/* Hero, as a monster which is capable of an exploding attack mattk, is
+ * exploding at a target monster mdef, or just exploding at nothing (e.g. with
+ * forcefight) if mdef is null.
+ */
+int
explum(struct monst *mdef, struct attack *mattk)
{
- boolean resistance; /* only for cold/fire/elec */
register int tmp = d((int) mattk->damn, (int) mattk->damd);
- You("explode!");
switch (mattk->adtyp) {
case AD_BLND:
- if (!resists_blnd(mdef)) {
+ if (mdef && !resists_blnd(mdef)) {
pline("%s is blinded by your flash of light!", Monnam(mdef));
mdef->mblinded = min((int) mdef->mblinded + tmp, 127);
mdef->mcansee = 0;
}
break;
case AD_HALU:
- if (haseyes(mdef->data) && mdef->mcansee) {
+ if (mdef && haseyes(mdef->data) && mdef->mcansee) {
pline("%s is affected by your flash of light!", Monnam(mdef));
mdef->mconf = 1;
}
break;
case AD_COLD:
- resistance = resists_cold(mdef);
- goto common;
case AD_FIRE:
- resistance = resists_fire(mdef);
- goto common;
case AD_ELEC:
- resistance = resists_elec(mdef);
- common:
- if (!resistance) {
- pline("%s gets blasted!", Monnam(mdef));
- mdef->mhp -= tmp;
- if (DEADMONSTER(mdef)) {
- killed(mdef);
- return MM_DEF_DIED;
- }
- } else {
- shieldeff(mdef->mx, mdef->my);
- if (is_golem(mdef->data))
- golemeffects(mdef, (int) mattk->adtyp, tmp);
- else
- pline_The("blast doesn't seem to affect %s.", mon_nam(mdef));
+ /* See comment in mon_explodes() and in zap.c for an explanation of this
+ * math. Here, the player is causing the explosion, so it should be in
+ * the +20 to +29 range instead of negative. */
+ explode(u.ux, u.uy, (mattk->adtyp - 1) + 20, tmp, MON_EXPLODE,
+ adtyp_to_expltype(mattk->adtyp));
+ if (mdef && DEADMONSTER(mdef)) {
+ /* Other monsters may have died too, but return this if the actual
+ * target died. */
+ return MM_DEF_DIED;
}
break;
default:
case AT_EXPL: /* automatic hit if next to */
dhit = -1;
wakeup(mon, TRUE);
+ You("explode!");
sum[i] = explum(mon, mattk);
break;
boolean see_it = cansee(x, y), yourzap;
int rangemod = 0, abstype = abs(type) % 10;
+ if (type == PHYS_EXPL_TYPE) {
+ /* this won't have any effect on the floor */
+ return -1000; /* not a zap anyway, shouldn't matter */
+ }
+
switch (abstype) {
case ZT_FIRE:
t = t_at(x, y);