wand/spell/breath killer reason

Extend "killed by the touch of death inflicted by <monster>" to buzz().
"Killed by a bolt of cold" becomes "killed by a bolt of cold zapped by
<monster>" or "killed by a blast of cold" becomes "killed by a blast
of cold exhaled by <monster>" and so forth.

More work than expected; the zap code isn't passed enough context.

BZ_M_WAND() was producing the wrong value for wands zapped by monsters.
This commit is contained in:
PatR
2023-03-07 02:47:42 -08:00
parent 8e6cf385b1
commit 4021a63bcf
12 changed files with 135 additions and 52 deletions

View File

@@ -530,6 +530,10 @@ prediscovered weapons adjustmens: only knights and samurai know polearms;
if the move counter ever reaches 1000000000, end the game
knights get no metal armor penalty for clerical spells
change touch of death from instadeath to maxhp reduction and damage
report cause of death due to touch of death as "killed by the touch of death
inflicted by <monster>" instead of just "killed by a touch of death"
report cause of death due to wand/spell/breath as "killed by <flash damage>
zapped/cast/exhaled by <monster>" instead of just "killed by <damage>"
dying from being level-drained below level 1 killed hero without saying so
and jumped straight to "do you want your possessions identified?"
conflict will now consider your charisma and requires line of sight

View File

@@ -793,6 +793,9 @@ struct instance_globals_b {
/* pickup.c */
boolean bucx_filter;
/* zap.c */
struct monst *buzzer; /* zapper/caster/breather who initiates buzz() */
boolean havestate;
unsigned long magic; /* validate that structure layout is preserved */
};

View File

@@ -1322,6 +1322,7 @@ extern boolean usmellmon(struct permonst *);
extern int castmu(struct monst *, struct attack *, boolean, boolean);
extern void touch_of_death(struct monst *);
extern char *death_inflicted_by(char *, const char *, struct monst *);
extern int buzzmu(struct monst *, struct attack *);
/* ### mdlib.c ### */

View File

@@ -677,12 +677,12 @@ enum getobj_callback_returns {
#define BZ_U_SPELL(bztyp) (10 + (bztyp))
/* hero breathing as a monster */
#define BZ_U_BREATH(bztyp) (20 + (bztyp))
/* monster shooting a wand */
#define BZ_M_WAND(bztyp) (-0 - (bztyp))
/* monster casting a spell */
#define BZ_M_SPELL(bztyp) (-10 - (bztyp))
/* monster breathing */
#define BZ_M_BREATH(bztyp) (-20 - (bztyp))
/* monster shooting a wand */
#define BZ_M_WAND(bztyp) (-30 - (bztyp))
/*
* option setting restrictions

View File

@@ -252,6 +252,9 @@ const struct instance_globals_b g_init_b = {
UNDEFINED_PTR, /* bbubbles */
/* pickup.c */
FALSE, /* bucx_filter */
/* zap.c */
NULL, /* buzzer -- monst that zapped/cast/breathed to initiate buzz() */
TRUE, /* havestate*/
IVMAGIC /* b_magic to validate that structure layout has been preserved */
};

View File

@@ -39,7 +39,6 @@ static void cursetxt(struct monst *, boolean);
static int choose_magic_spell(int);
static int choose_clerical_spell(int);
static int m_cure_self(struct monst *, int);
static char *death_inflicted_by(char *, const char *, struct monst *);
static void cast_wizard_spell(struct monst *, int, int);
static void cast_cleric_spell(struct monst *, int, int);
static boolean is_undirected_spell(unsigned int, int);
@@ -375,7 +374,7 @@ touch_of_death(struct monst *mtmp)
}
/* give a reason for death by some monster spells */
static char *
char *
death_inflicted_by(
char *outbuf, /* assumed big enough; pm_names are short */
const char *deathreason, /* cause of death */
@@ -909,7 +908,7 @@ spell_would_be_useless(struct monst *mtmp, unsigned int adtyp, int spellnum)
/* monster uses spell (ranged) */
int
buzzmu(register struct monst *mtmp, register struct attack *mattk)
buzzmu(struct monst *mtmp, struct attack *mattk)
{
/* don't print constant stream of curse messages for 'normal'
spellcasting monsters at range */
@@ -925,8 +924,10 @@ buzzmu(register struct monst *mtmp, register struct attack *mattk)
if (canseemon(mtmp))
pline("%s zaps you with a %s!", Monnam(mtmp),
flash_str(BZ_OFS_AD(mattk->adtyp), FALSE));
buzz(BZ_M_SPELL(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn, mtmp->mx,
mtmp->my, sgn(gt.tbx), sgn(gt.tby));
gb.buzzer = mtmp;
buzz(BZ_M_SPELL(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn,
mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby));
gb.buzzer = 0;
return MM_HIT;
}
return MM_MISS;

View File

@@ -927,8 +927,10 @@ breamm(struct monst* mtmp, struct attack* mattk, struct monst* mtarg)
if (canseemon(mtmp))
pline("%s breathes %s!",
Monnam(mtmp), breathwep_name(typ));
gb.buzzer = mtmp;
dobuzz(BZ_M_BREATH(BZ_OFS_AD(typ)), (int) mattk->damn,
mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), utarget);
gb.buzzer = 0;
nomul(0);
/* breath runs out sometimes. Also, give monster some
* cunning; don't breath if the target fell asleep.

View File

@@ -1659,19 +1659,27 @@ use_offensive(struct monst *mtmp)
if (oseen)
makeknown(otmp->otyp);
gm.m_using = TRUE;
gc.current_wand = otmp;
gb.buzzer = mtmp;
buzz(BZ_M_WAND(BZ_OFS_WAN(otmp->otyp)),
(otmp->otyp == WAN_MAGIC_MISSILE) ? 2 : 6, mtmp->mx, mtmp->my,
sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my));
gb.buzzer = 0;
gc.current_wand = 0;
gm.m_using = FALSE;
return (DEADMONSTER(mtmp)) ? 1 : 2;
case MUSE_FIRE_HORN:
case MUSE_FROST_HORN:
mplayhorn(mtmp, otmp, FALSE);
gm.m_using = TRUE;
gb.buzzer = mtmp;
gc.current_wand = otmp; /* needed by zhitu() */
buzz(BZ_M_WAND(BZ_OFS_AD((otmp->otyp == FROST_HORN) ? AD_COLD
: AD_FIRE)),
rn1(6, 6), mtmp->mx, mtmp->my, sgn(mtmp->mux - mtmp->mx),
sgn(mtmp->muy - mtmp->my));
gb.buzzer = 0;
gc.current_wand = 0;
gm.m_using = FALSE;
return (DEADMONSTER(mtmp)) ? 1 : 2;
case MUSE_WAN_TELEPORTATION:

View File

@@ -615,12 +615,15 @@ do_improvisation(struct obj* instr)
losehp(damage, buf, KILLED_BY); /* fire or frost damage */
}
} else {
int type = BZ_OFS_AD((instr->otyp == FROST_HORN) ? AD_COLD : AD_FIRE);
int type = BZ_OFS_AD((instr->otyp == FROST_HORN) ? AD_COLD
: AD_FIRE);
if (!Blind)
pline("A %s blasts out of the horn!", flash_str(type, FALSE));
Hero_playnotes(obj_to_instr(&itmp), improvisation, 50);
gc.current_wand = instr;
ubuzz(BZ_U_WAND(type), rn1(6, 6));
gc.current_wand = 0;
}
makeknown(instr->otyp);
break;

View File

@@ -753,9 +753,11 @@ in_your_sanctuary(
void
ghod_hitsu(struct monst *priest)
{
struct mkroom *troom;
struct monst *oldbuzzer;
struct obj *oldcurrwand;
coordxy x, y, ax, ay;
int roomno = (int) temple_occupied(u.urooms);
struct mkroom *troom;
if (!roomno || !has_shrine(priest))
return;
@@ -818,8 +820,14 @@ ghod_hitsu(struct monst *priest)
break;
}
buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 6, x, y, sgn(gt.tbx),
sgn(gt.tby)); /* bolt of lightning */
/* bolt of lightning cast by unspecified monster */
oldcurrwand = gc.current_wand;
gc.current_wand = 0;
oldbuzzer = gb.buzzer;
gb.buzzer = 0;
buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 6, x, y, sgn(gt.tbx), sgn(gt.tby));
gb.buzzer = oldbuzzer;
gc.current_wand = oldcurrwand;
exercise(A_WIS, FALSE);
}

View File

@@ -1721,9 +1721,11 @@ do_storms(void)
if (count < 100) {
dirx = rn2(3) - 1;
diry = rn2(3) - 1;
if (dirx != 0 || diry != 0)
buzz(-15, /* "monster" LIGHTNING spell */
8, x, y, dirx, diry);
if (dirx != 0 || diry != 0) {
/* BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)): monster LIGHTNING spell */
gb.buzzer = 0; /* unspecified attacker */
buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 8, x, y, dirx, diry);
}
}
}

124
src/zap.c
View File

@@ -52,25 +52,31 @@ static void wishcmdassist(int);
#define M_IN_WATER(ptr) \
((ptr)->mlet == S_EEL || amphibious(ptr) || is_swimmer(ptr))
static const char are_blinded_by_the_flash[] =
"are blinded by the flash!";
static const char are_blinded_by_the_flash[] = "are blinded by the flash!";
static const char *const flash_types[] =
{
"magic missile", /* Wands must be 0-9 */
"bolt of fire", "bolt of cold", "sleep ray", "death ray",
"bolt of lightning", "", "", "", "",
/*
* FIXME:
* flash_types[0] for wand of magic missile is ambiguous.
* A positive index means zapped/cast/breathed by hero.
* A negative index means zapped/cast/breathed by a monster.
* Since abs(-0)==abs(0), there's no way to tell who zapped a wand of
* magic missile by just checking the index.
*/
static const char *const flash_types[] = {
"magic missile", /* Wands must be 0-9 */
"bolt of fire", "bolt of cold", "sleep ray", "death ray",
"bolt of lightning", "", "", "", "",
"magic missile", /* Spell equivalents must be 10-19 */
"fireball", "cone of cold", "sleep ray", "finger of death",
"bolt of lightning", /* there is no spell, used for retribution */
"", "", "", "",
"magic missile", /* Spell equivalents must be 10-19 */
"fireball", "cone of cold", "sleep ray", "finger of death",
"bolt of lightning", /* there is no spell, used for retribution */
"", "", "", "",
"blast of missiles", /* Dragon breath equivalents 20-29*/
"blast of fire", "blast of frost", "blast of sleep gas",
"blast of disintegration", "blast of lightning",
"blast of poison gas", "blast of acid", "", ""
};
"blast of missiles", /* Dragon breath equivalents 20-29*/
"blast of fire", "blast of frost", "blast of sleep gas",
"blast of disintegration", "blast of lightning",
"blast of poison gas", "blast of acid", "", ""
};
/*
* Recognizing unseen wands by zapping: in 3.4.3 and earlier, zapping
@@ -2854,10 +2860,10 @@ ubreatheu(struct attack *mattk)
/* light damages hero in gremlin form */
int
lightdamage(struct obj *obj, /* item making light (fake book if spell) */
boolean ordinary, /* wand/camera zap vs wand destruction */
int amt) /* pseudo-damage used to determine blindness
duration */
lightdamage(
struct obj *obj, /* item making light (fake book if spell) */
boolean ordinary, /* wand/camera zap vs wand destruction */
int amt) /* pseudo-damage used to determine blindness duration */
{
char buf[BUFSZ];
const char *how;
@@ -3300,7 +3306,8 @@ weffects(struct obj *obj)
else if (otyp >= SPE_MAGIC_MISSILE && otyp <= SPE_FINGER_OF_DEATH)
ubuzz(BZ_U_SPELL(BZ_OFS_SPE(otyp)), u.ulevel / 2 + 1);
else if (otyp >= WAN_MAGIC_MISSILE && otyp <= WAN_LIGHTNING)
ubuzz(BZ_U_WAND(BZ_OFS_WAN(otyp)), (otyp == WAN_MAGIC_MISSILE) ? 2 : 6);
ubuzz(BZ_U_WAND(BZ_OFS_WAN(otyp)),
(otyp == WAN_MAGIC_MISSILE) ? 2 : 6);
else
impossible("weffects: unexpected spell or wand");
disclose = TRUE;
@@ -4058,7 +4065,10 @@ zhitm(
}
static void
zhitu(int type, int nd, const char *fltxt, coordxy sx, coordxy sy)
zhitu(
int type, int nd,
const char *fltxt,
coordxy sx, coordxy sy)
{
int dam = 0, abstyp = abs(type);
@@ -4197,11 +4207,47 @@ zhitu(int type, int nd, const char *fltxt, coordxy sx, coordxy sy)
break;
}
/* Half_spell_damage protection yields half-damage for wands & spells,
including hero's own ricochets; breath attacks do full damage */
if (dam && Half_spell_damage && !(abstyp >= 20 && abstyp <= 29))
dam = (dam + 1) / 2;
losehp(dam, fltxt, KILLED_BY_AN);
/*
* 3.7: when fatal, this used to yield "Killed by <fltxt>." without any
* information about who was responsible. Now 'buzzer' is used to try
* to supply "zapped/cast/breathed by <mon> [imitating <other_mon>]."
*
* Room for improvement: there is no monster available when player is
* hit by divine lighting or by Plane of Air thunderstorm so cause of
* death remains "killed by a bolt of lightning" w/o extra explanation.
*
* Wand of death, spell of finger of death, and disintegration breath
* don't use this routine so don't include 'inflicted by'.
*/
{
char kbuf[BUFSZ];
struct obj *otmp = gc.current_wand;
/* fire horn and frost horn get handled as wands by caller */
const char *verb = (abstyp < 10) /* wand */
? ((otmp && otmp->oclass == TOOL_CLASS) ? "played"
: "zapped")
: (abstyp < 20) ? "cast"
: (abstyp < 30) ? "exhaled"
: "imagined"; /* should never happen */
if (type < 0 || (type == 0 && gb.buzzer != 0)) {
/* if gb.buzzer is Null, kbuf[] will end up with just <fltxt> */
(void) death_inflicted_by(kbuf, fltxt, gb.buzzer);
/* change "death inflicted by mon" to "death <verb> by mon" */
if (gb.buzzer)
(void) strsubst(kbuf, "inflicted", verb);
} else {
/* FIXME: "zapped by herself" is suitable for a rebound;
"zapped at herself" would be better if player explicitly
targeted hero */
Sprintf(kbuf, "%s %s by %sself", fltxt, verb, uhim());
}
/* Half_spell_damage protection yields half-damage for wands & spells,
including hero's own ricochets; breath attacks do full damage */
if (dam && Half_spell_damage && abstyp < 20)
dam = (dam + 1) / 2;
losehp(dam, kbuf, KILLED_BY_AN);
}
return;
}
@@ -4210,10 +4256,10 @@ zhitu(int type, int nd, const char *fltxt, coordxy sx, coordxy sy)
* at position x,y; return the number of objects burned
*/
int
burn_floor_objects(coordxy x, coordxy y,
boolean give_feedback, /* caller needs to decide about
visibility checks */
boolean u_caused)
burn_floor_objects(
coordxy x, coordxy y,
boolean give_feedback, /* caller needs to decide about visibility checks */
boolean u_caused)
{
struct obj *obj, *obj2;
long i, scrquan, delquan;
@@ -4288,9 +4334,10 @@ zap_hit(int ac,
}
static void
disintegrate_mon(struct monst *mon,
int type, /* hero vs other */
const char *fltxt)
disintegrate_mon(
struct monst *mon,
int type, /* hero vs other */
const char *fltxt)
{
struct obj *otmp, *otmp2, *m_amulet = mlifesaver(mon);
@@ -4428,7 +4475,8 @@ dobuzz(
if (type >= 0)
mon->mstrategy &= ~STRAT_WAITMASK;
buzzmonst:
gn.notonhead = (mon->mx != gb.bhitpos.x || mon->my != gb.bhitpos.y);
gn.notonhead = (mon->mx != gb.bhitpos.x
|| mon->my != gb.bhitpos.y);
if (zap_hit(find_mac(mon), spell_type)) {
if (mon_reflects(mon, (char *) 0)) {
if (cansee(mon->mx, mon->my)) {
@@ -4667,9 +4715,9 @@ melt_ice(coordxy x, coordxy y, const char *msg)
* permanent instead.
*/
void
start_melt_ice_timeout(coordxy x, coordxy y,
long min_time) /* <x,y>'s old melt timeout (deleted by
time we get here) */
start_melt_ice_timeout(
coordxy x, coordxy y,
long min_time) /* <x,y>'s old melt timeout (deleted by time we get here) */
{
int when;
long where;