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
+4
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 if the move counter ever reaches 1000000000, end the game
knights get no metal armor penalty for clerical spells knights get no metal armor penalty for clerical spells
change touch of death from instadeath to maxhp reduction and damage 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 dying from being level-drained below level 1 killed hero without saying so
and jumped straight to "do you want your possessions identified?" and jumped straight to "do you want your possessions identified?"
conflict will now consider your charisma and requires line of sight conflict will now consider your charisma and requires line of sight
+3
View File
@@ -793,6 +793,9 @@ struct instance_globals_b {
/* pickup.c */ /* pickup.c */
boolean bucx_filter; boolean bucx_filter;
/* zap.c */
struct monst *buzzer; /* zapper/caster/breather who initiates buzz() */
boolean havestate; boolean havestate;
unsigned long magic; /* validate that structure layout is preserved */ unsigned long magic; /* validate that structure layout is preserved */
}; };
+1
View File
@@ -1322,6 +1322,7 @@ extern boolean usmellmon(struct permonst *);
extern int castmu(struct monst *, struct attack *, boolean, boolean); extern int castmu(struct monst *, struct attack *, boolean, boolean);
extern void touch_of_death(struct monst *); extern void touch_of_death(struct monst *);
extern char *death_inflicted_by(char *, const char *, struct monst *);
extern int buzzmu(struct monst *, struct attack *); extern int buzzmu(struct monst *, struct attack *);
/* ### mdlib.c ### */ /* ### mdlib.c ### */
+2 -2
View File
@@ -677,12 +677,12 @@ enum getobj_callback_returns {
#define BZ_U_SPELL(bztyp) (10 + (bztyp)) #define BZ_U_SPELL(bztyp) (10 + (bztyp))
/* hero breathing as a monster */ /* hero breathing as a monster */
#define BZ_U_BREATH(bztyp) (20 + (bztyp)) #define BZ_U_BREATH(bztyp) (20 + (bztyp))
/* monster shooting a wand */
#define BZ_M_WAND(bztyp) (-0 - (bztyp))
/* monster casting a spell */ /* monster casting a spell */
#define BZ_M_SPELL(bztyp) (-10 - (bztyp)) #define BZ_M_SPELL(bztyp) (-10 - (bztyp))
/* monster breathing */ /* monster breathing */
#define BZ_M_BREATH(bztyp) (-20 - (bztyp)) #define BZ_M_BREATH(bztyp) (-20 - (bztyp))
/* monster shooting a wand */
#define BZ_M_WAND(bztyp) (-30 - (bztyp))
/* /*
* option setting restrictions * option setting restrictions
+3
View File
@@ -252,6 +252,9 @@ const struct instance_globals_b g_init_b = {
UNDEFINED_PTR, /* bbubbles */ UNDEFINED_PTR, /* bbubbles */
/* pickup.c */ /* pickup.c */
FALSE, /* bucx_filter */ FALSE, /* bucx_filter */
/* zap.c */
NULL, /* buzzer -- monst that zapped/cast/breathed to initiate buzz() */
TRUE, /* havestate*/ TRUE, /* havestate*/
IVMAGIC /* b_magic to validate that structure layout has been preserved */ IVMAGIC /* b_magic to validate that structure layout has been preserved */
}; };
+6 -5
View File
@@ -39,7 +39,6 @@ static void cursetxt(struct monst *, boolean);
static int choose_magic_spell(int); static int choose_magic_spell(int);
static int choose_clerical_spell(int); static int choose_clerical_spell(int);
static int m_cure_self(struct monst *, 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_wizard_spell(struct monst *, int, int);
static void cast_cleric_spell(struct monst *, int, int); static void cast_cleric_spell(struct monst *, int, int);
static boolean is_undirected_spell(unsigned 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 */ /* give a reason for death by some monster spells */
static char * char *
death_inflicted_by( death_inflicted_by(
char *outbuf, /* assumed big enough; pm_names are short */ char *outbuf, /* assumed big enough; pm_names are short */
const char *deathreason, /* cause of death */ 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) */ /* monster uses spell (ranged) */
int 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' /* don't print constant stream of curse messages for 'normal'
spellcasting monsters at range */ spellcasting monsters at range */
@@ -925,8 +924,10 @@ buzzmu(register struct monst *mtmp, register struct attack *mattk)
if (canseemon(mtmp)) if (canseemon(mtmp))
pline("%s zaps you with a %s!", Monnam(mtmp), pline("%s zaps you with a %s!", Monnam(mtmp),
flash_str(BZ_OFS_AD(mattk->adtyp), FALSE)); flash_str(BZ_OFS_AD(mattk->adtyp), FALSE));
buzz(BZ_M_SPELL(BZ_OFS_AD(mattk->adtyp)), (int) mattk->damn, mtmp->mx, gb.buzzer = mtmp;
mtmp->my, sgn(gt.tbx), sgn(gt.tby)); 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_HIT;
} }
return MM_MISS; return MM_MISS;
+2
View File
@@ -927,8 +927,10 @@ breamm(struct monst* mtmp, struct attack* mattk, struct monst* mtarg)
if (canseemon(mtmp)) if (canseemon(mtmp))
pline("%s breathes %s!", pline("%s breathes %s!",
Monnam(mtmp), breathwep_name(typ)); Monnam(mtmp), breathwep_name(typ));
gb.buzzer = mtmp;
dobuzz(BZ_M_BREATH(BZ_OFS_AD(typ)), (int) mattk->damn, dobuzz(BZ_M_BREATH(BZ_OFS_AD(typ)), (int) mattk->damn,
mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), utarget); mtmp->mx, mtmp->my, sgn(gt.tbx), sgn(gt.tby), utarget);
gb.buzzer = 0;
nomul(0); nomul(0);
/* breath runs out sometimes. Also, give monster some /* breath runs out sometimes. Also, give monster some
* cunning; don't breath if the target fell asleep. * cunning; don't breath if the target fell asleep.
+8
View File
@@ -1659,19 +1659,27 @@ use_offensive(struct monst *mtmp)
if (oseen) if (oseen)
makeknown(otmp->otyp); makeknown(otmp->otyp);
gm.m_using = TRUE; gm.m_using = TRUE;
gc.current_wand = otmp;
gb.buzzer = mtmp;
buzz(BZ_M_WAND(BZ_OFS_WAN(otmp->otyp)), buzz(BZ_M_WAND(BZ_OFS_WAN(otmp->otyp)),
(otmp->otyp == WAN_MAGIC_MISSILE) ? 2 : 6, mtmp->mx, mtmp->my, (otmp->otyp == WAN_MAGIC_MISSILE) ? 2 : 6, mtmp->mx, mtmp->my,
sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my)); sgn(mtmp->mux - mtmp->mx), sgn(mtmp->muy - mtmp->my));
gb.buzzer = 0;
gc.current_wand = 0;
gm.m_using = FALSE; gm.m_using = FALSE;
return (DEADMONSTER(mtmp)) ? 1 : 2; return (DEADMONSTER(mtmp)) ? 1 : 2;
case MUSE_FIRE_HORN: case MUSE_FIRE_HORN:
case MUSE_FROST_HORN: case MUSE_FROST_HORN:
mplayhorn(mtmp, otmp, FALSE); mplayhorn(mtmp, otmp, FALSE);
gm.m_using = TRUE; 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 buzz(BZ_M_WAND(BZ_OFS_AD((otmp->otyp == FROST_HORN) ? AD_COLD
: AD_FIRE)), : AD_FIRE)),
rn1(6, 6), mtmp->mx, mtmp->my, sgn(mtmp->mux - mtmp->mx), rn1(6, 6), mtmp->mx, mtmp->my, sgn(mtmp->mux - mtmp->mx),
sgn(mtmp->muy - mtmp->my)); sgn(mtmp->muy - mtmp->my));
gb.buzzer = 0;
gc.current_wand = 0;
gm.m_using = FALSE; gm.m_using = FALSE;
return (DEADMONSTER(mtmp)) ? 1 : 2; return (DEADMONSTER(mtmp)) ? 1 : 2;
case MUSE_WAN_TELEPORTATION: case MUSE_WAN_TELEPORTATION:
+4 -1
View File
@@ -615,12 +615,15 @@ do_improvisation(struct obj* instr)
losehp(damage, buf, KILLED_BY); /* fire or frost damage */ losehp(damage, buf, KILLED_BY); /* fire or frost damage */
} }
} else { } 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) if (!Blind)
pline("A %s blasts out of the horn!", flash_str(type, FALSE)); pline("A %s blasts out of the horn!", flash_str(type, FALSE));
Hero_playnotes(obj_to_instr(&itmp), improvisation, 50); Hero_playnotes(obj_to_instr(&itmp), improvisation, 50);
gc.current_wand = instr;
ubuzz(BZ_U_WAND(type), rn1(6, 6)); ubuzz(BZ_U_WAND(type), rn1(6, 6));
gc.current_wand = 0;
} }
makeknown(instr->otyp); makeknown(instr->otyp);
break; break;
+11 -3
View File
@@ -753,9 +753,11 @@ in_your_sanctuary(
void void
ghod_hitsu(struct monst *priest) ghod_hitsu(struct monst *priest)
{ {
struct mkroom *troom;
struct monst *oldbuzzer;
struct obj *oldcurrwand;
coordxy x, y, ax, ay; coordxy x, y, ax, ay;
int roomno = (int) temple_occupied(u.urooms); int roomno = (int) temple_occupied(u.urooms);
struct mkroom *troom;
if (!roomno || !has_shrine(priest)) if (!roomno || !has_shrine(priest))
return; return;
@@ -818,8 +820,14 @@ ghod_hitsu(struct monst *priest)
break; break;
} }
buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 6, x, y, sgn(gt.tbx), /* bolt of lightning cast by unspecified monster */
sgn(gt.tby)); /* bolt of lightning */ 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); exercise(A_WIS, FALSE);
} }
+5 -3
View File
@@ -1721,9 +1721,11 @@ do_storms(void)
if (count < 100) { if (count < 100) {
dirx = rn2(3) - 1; dirx = rn2(3) - 1;
diry = rn2(3) - 1; diry = rn2(3) - 1;
if (dirx != 0 || diry != 0) if (dirx != 0 || diry != 0) {
buzz(-15, /* "monster" LIGHTNING spell */ /* BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)): monster LIGHTNING spell */
8, x, y, dirx, diry); gb.buzzer = 0; /* unspecified attacker */
buzz(BZ_M_SPELL(BZ_OFS_AD(AD_ELEC)), 8, x, y, dirx, diry);
}
} }
} }
+86 -38
View File
@@ -52,25 +52,31 @@ static void wishcmdassist(int);
#define M_IN_WATER(ptr) \ #define M_IN_WATER(ptr) \
((ptr)->mlet == S_EEL || amphibious(ptr) || is_swimmer(ptr)) ((ptr)->mlet == S_EEL || amphibious(ptr) || is_swimmer(ptr))
static const char are_blinded_by_the_flash[] = static const char are_blinded_by_the_flash[] = "are blinded by the flash!";
"are blinded by the flash!";
static const char *const flash_types[] = /*
{ * FIXME:
"magic missile", /* Wands must be 0-9 */ * flash_types[0] for wand of magic missile is ambiguous.
"bolt of fire", "bolt of cold", "sleep ray", "death ray", * A positive index means zapped/cast/breathed by hero.
"bolt of lightning", "", "", "", "", * 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 */ "magic missile", /* Spell equivalents must be 10-19 */
"fireball", "cone of cold", "sleep ray", "finger of death", "fireball", "cone of cold", "sleep ray", "finger of death",
"bolt of lightning", /* there is no spell, used for retribution */ "bolt of lightning", /* there is no spell, used for retribution */
"", "", "", "", "", "", "", "",
"blast of missiles", /* Dragon breath equivalents 20-29*/ "blast of missiles", /* Dragon breath equivalents 20-29*/
"blast of fire", "blast of frost", "blast of sleep gas", "blast of fire", "blast of frost", "blast of sleep gas",
"blast of disintegration", "blast of lightning", "blast of disintegration", "blast of lightning",
"blast of poison gas", "blast of acid", "", "" "blast of poison gas", "blast of acid", "", ""
}; };
/* /*
* Recognizing unseen wands by zapping: in 3.4.3 and earlier, zapping * 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 */ /* light damages hero in gremlin form */
int int
lightdamage(struct obj *obj, /* item making light (fake book if spell) */ lightdamage(
boolean ordinary, /* wand/camera zap vs wand destruction */ struct obj *obj, /* item making light (fake book if spell) */
int amt) /* pseudo-damage used to determine blindness boolean ordinary, /* wand/camera zap vs wand destruction */
duration */ int amt) /* pseudo-damage used to determine blindness duration */
{ {
char buf[BUFSZ]; char buf[BUFSZ];
const char *how; const char *how;
@@ -3300,7 +3306,8 @@ weffects(struct obj *obj)
else if (otyp >= SPE_MAGIC_MISSILE && otyp <= SPE_FINGER_OF_DEATH) else if (otyp >= SPE_MAGIC_MISSILE && otyp <= SPE_FINGER_OF_DEATH)
ubuzz(BZ_U_SPELL(BZ_OFS_SPE(otyp)), u.ulevel / 2 + 1); ubuzz(BZ_U_SPELL(BZ_OFS_SPE(otyp)), u.ulevel / 2 + 1);
else if (otyp >= WAN_MAGIC_MISSILE && otyp <= WAN_LIGHTNING) 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 else
impossible("weffects: unexpected spell or wand"); impossible("weffects: unexpected spell or wand");
disclose = TRUE; disclose = TRUE;
@@ -4058,7 +4065,10 @@ zhitm(
} }
static void 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); int dam = 0, abstyp = abs(type);
@@ -4197,11 +4207,47 @@ zhitu(int type, int nd, const char *fltxt, coordxy sx, coordxy sy)
break; break;
} }
/* Half_spell_damage protection yields half-damage for wands & spells, /*
including hero's own ricochets; breath attacks do full damage */ * 3.7: when fatal, this used to yield "Killed by <fltxt>." without any
if (dam && Half_spell_damage && !(abstyp >= 20 && abstyp <= 29)) * information about who was responsible. Now 'buzzer' is used to try
dam = (dam + 1) / 2; * to supply "zapped/cast/breathed by <mon> [imitating <other_mon>]."
losehp(dam, fltxt, KILLED_BY_AN); *
* 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; 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 * at position x,y; return the number of objects burned
*/ */
int int
burn_floor_objects(coordxy x, coordxy y, burn_floor_objects(
boolean give_feedback, /* caller needs to decide about coordxy x, coordxy y,
visibility checks */ boolean give_feedback, /* caller needs to decide about visibility checks */
boolean u_caused) boolean u_caused)
{ {
struct obj *obj, *obj2; struct obj *obj, *obj2;
long i, scrquan, delquan; long i, scrquan, delquan;
@@ -4288,9 +4334,10 @@ zap_hit(int ac,
} }
static void static void
disintegrate_mon(struct monst *mon, disintegrate_mon(
int type, /* hero vs other */ struct monst *mon,
const char *fltxt) int type, /* hero vs other */
const char *fltxt)
{ {
struct obj *otmp, *otmp2, *m_amulet = mlifesaver(mon); struct obj *otmp, *otmp2, *m_amulet = mlifesaver(mon);
@@ -4428,7 +4475,8 @@ dobuzz(
if (type >= 0) if (type >= 0)
mon->mstrategy &= ~STRAT_WAITMASK; mon->mstrategy &= ~STRAT_WAITMASK;
buzzmonst: 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 (zap_hit(find_mac(mon), spell_type)) {
if (mon_reflects(mon, (char *) 0)) { if (mon_reflects(mon, (char *) 0)) {
if (cansee(mon->mx, mon->my)) { if (cansee(mon->mx, mon->my)) {
@@ -4667,9 +4715,9 @@ melt_ice(coordxy x, coordxy y, const char *msg)
* permanent instead. * permanent instead.
*/ */
void void
start_melt_ice_timeout(coordxy x, coordxy y, start_melt_ice_timeout(
long min_time) /* <x,y>'s old melt timeout (deleted by coordxy x, coordxy y,
time we get here) */ long min_time) /* <x,y>'s old melt timeout (deleted by time we get here) */
{ {
int when; int when;
long where; long where;