diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 33fe34b78d..34dece0ba6 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 diff --git a/include/extern.h b/include/extern.h index cec6fdbf81..548280a509 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 ### */ @@ -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); diff --git a/include/hack.h b/include/hack.h index e4cf6b77d3..42b1282e21 100644 --- a/include/hack.h +++ b/include/hack.h @@ -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 */ diff --git a/src/explode.c b/src/explode.c index 2a2e993dbc..e48ab61ac1 100644 --- a/src/explode.c +++ b/src/explode.c @@ -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. @@ -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 @@ -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++) @@ -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 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); @@ -479,6 +501,8 @@ explode( setmangry(mtmp, TRUE); } } + } + } /* Do your injury last */ if (uhurt) { @@ -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*/ diff --git a/src/hack.c b/src/hack.c index bcec86d9a1..838cbe6148 100644 --- a/src/hack.c +++ b/src/hack.c @@ -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(); } diff --git a/src/mhitm.c b/src/mhitm.c index 13e36af1f3..72ebb10c39 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -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)) { diff --git a/src/mhitu.c b/src/mhitu.c index 3050c35ced..0ba7f984f2 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1347,100 +1347,71 @@ gulpmu(struct monst *mtmp, struct attack *mattk) 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; diff --git a/src/mon.c b/src/mon.c index 80714d0525..423ff03172 100644 --- a/src/mon.c +++ b/src/mon.c @@ -2522,12 +2522,7 @@ corpse_chance( 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; } } diff --git a/src/monst.c b/src/monst.c index 2c7cb64242..1da91df093 100644 --- a/src/monst.c +++ b/src/monst.c @@ -324,21 +324,21 @@ NEARDATA struct permonst mons_init[] = { 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), diff --git a/src/uhitm.c b/src/uhitm.c index 36518c71fc..6a5e193a5b 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -18,7 +18,6 @@ static boolean hmon_hitmon(struct monst *, struct obj *, int, int); 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 *); @@ -4127,49 +4126,41 @@ damageum( 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: @@ -4745,6 +4736,7 @@ hmonas(struct monst *mon) case AT_EXPL: /* automatic hit if next to */ dhit = -1; wakeup(mon, TRUE); + You("explode!"); sum[i] = explum(mon, mattk); break; diff --git a/src/zap.c b/src/zap.c index f9e81498fa..3e568433b7 100644 --- a/src/zap.c +++ b/src/zap.c @@ -4532,6 +4532,11 @@ zap_over_floor(xchar x, xchar y, int type, boolean *shopdamage, 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);