From 4b32f8b3bd3ea146f2b058b70ca849ddfecd6488 Mon Sep 17 00:00:00 2001 From: Michael Meyer Date: Tue, 4 Oct 2022 17:13:58 -0400 Subject: [PATCH 1/5] Fix: 'weaken target' spell against poly'd hero... ...could leave hero in creature form with negative u.mh losestr can subtract HP, but doesn't directly kill its target. The caller is responsible for possibly killing the hero if losestr reduces her HP to 0 or lower; most callers do this by combining losestr with a losehp call, which can kill off the hero if necessary. MGC_WEAKEN_YOU calls done_in_by if u.uhp < 1 after losestr, but didn't handle the Upolyd u.mh case, so could leave a polymorphed hero with negative health. Add a rehumanize call in that case. This could also be done by changing losestr to call losehp itself for the HP loss it deals out, but this would interfere with cast_wizard_spell's use of done_in_by to generate the death reason: either all strength loss is described one way ("terminal frailty" or something -- not great) or else losestr must be passed a death reason and is described a different way than other attack spells (because it wouldn't go through done_in_by). --- src/mcastu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mcastu.c b/src/mcastu.c index 1179988cb..f83a38689 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -468,6 +468,8 @@ cast_wizard_spell(struct monst *mtmp, int dmg, int spellnum) losestr(rnd(dmg)); if (u.uhp < 1) done_in_by(mtmp, DIED); + else if (Upolyd && u.mh < 1) + rehumanize(); } dmg = 0; break; From c0dfa40cd32284701df6d93c8b78ff129e86e219 Mon Sep 17 00:00:00 2001 From: Michael Meyer Date: Tue, 4 Oct 2022 17:26:23 -0400 Subject: [PATCH 2/5] Don't use boolean for losehp killer format type Killer format isn't a boolean, since it has 3 possible values (KILLED_BY_AN, KILLED_BY, NO_KILLER_PREFIX). It shouldn't make any difference behind the scenes, but it's confusing to use 'boolean' for it. --- include/extern.h | 2 +- src/hack.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/extern.h b/include/extern.h index 68adedd8e..e1f1b9f6d 100644 --- a/include/extern.h +++ b/include/extern.h @@ -977,7 +977,7 @@ extern int monster_nearby(void); extern void end_running(boolean); extern void nomul(int); extern void unmul(const char *); -extern void losehp(int, const char *, boolean); +extern void losehp(int, const char *, schar); extern int weight_cap(void); extern int inv_weight(void); extern int near_capacity(void); diff --git a/src/hack.c b/src/hack.c index dabe05e7f..aff4a2a99 100644 --- a/src/hack.c +++ b/src/hack.c @@ -3645,7 +3645,7 @@ maybe_wail(void) } void -losehp(int n, const char *knam, boolean k_format) +losehp(int n, const char *knam, schar k_format) { #if 0 /* code below is prepared to handle negative 'loss' so don't add this * until we've verified that no callers intentionally rely on that */ From 70fe2ce5cdeb99badd5f2d932adee08df9657a4c Mon Sep 17 00:00:00 2001 From: Michael Meyer Date: Tue, 4 Oct 2022 17:37:35 -0400 Subject: [PATCH 3/5] Don't make callers responsible for losestr death Remove callers' responsibility to deal with possible hero death when calling losestr. This is less fragile and error-prone than leaving it in the caller's hands, but it means that death from the monster spell 'weaken target' no longer goes through done_in_by, and the death reason is no longer "killed by ". --- include/extern.h | 2 +- src/attrib.c | 11 ++++++++--- src/eat.c | 11 ++++++----- src/fountain.c | 2 +- src/mcastu.c | 6 +----- src/spell.c | 3 ++- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/include/extern.h b/include/extern.h index e1f1b9f6d..2f7e2b3c8 100644 --- a/include/extern.h +++ b/include/extern.h @@ -114,7 +114,7 @@ extern struct obj *has_magic_key(struct monst *); extern boolean adjattrib(int, int, int); extern void gainstr(struct obj *, int, boolean); -extern void losestr(int); +extern void losestr(int, const char *, schar); extern void poisontell(int, boolean); extern void poisoned(const char *, int, const char *, int, boolean); extern void change_luck(schar); diff --git a/src/attrib.c b/src/attrib.c index 43f3759a3..e74abd3e2 100644 --- a/src/attrib.c +++ b/src/attrib.c @@ -211,19 +211,24 @@ gainstr(struct obj *otmp, int incr, boolean givemsg) /* may kill you; cause may be poison or monster like 'a' */ void -losestr(int num) +losestr(int num, const char *knam, schar k_format) { int uhpmin = minuhpmax(1), olduhpmax = u.uhpmax; int ustr = ABASE(A_STR) - num; + /* in case HP loss kills the hero once Str hits the minimum */ + if (!knam || !*knam) { + knam = "terminal frailty"; + k_format = KILLED_BY; + } + while (ustr < 3) { ++ustr; --num; + losehp(6, knam, k_format); if (Upolyd) { - u.mh -= 6; u.mhmax -= 6; } else { - u.uhp -= 6; setuhpmax(u.uhpmax - 6); } g.context.botl = TRUE; diff --git a/src/eat.c b/src/eat.c index 1ebf42014..c7108eafa 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1815,9 +1815,9 @@ eatcorpse(struct obj *otmp) tp++; pline("Ecch - that must have been poisonous!"); if (!Poison_resistance) { - losestr(rnd(4)); - losehp(rnd(15), !glob ? "poisonous corpse" : "poisonous glob", - KILLED_BY_AN); + const char *knam = !glob ? "poisonous corpse" : "poisonous glob"; + losestr(rnd(4), knam, KILLED_BY_AN); + losehp(rnd(15), knam, KILLED_BY_AN); } else You("seem unaffected by the poison."); @@ -2753,8 +2753,9 @@ doeat(void) if (otmp->oclass == WEAPON_CLASS && otmp->opoisoned) { pline("Ecch - that must have been poisonous!"); if (!Poison_resistance) { - losestr(rnd(4)); - losehp(rnd(15), xname(otmp), KILLED_BY_AN); + const char *knam = xname(otmp); + losestr(rnd(4), knam, KILLED_BY_AN); + losehp(rnd(15), knam, KILLED_BY_AN); } else You("seem unaffected by the poison."); } else if (!nodelicious) { diff --git a/src/fountain.c b/src/fountain.c index 13203b330..8791f39b3 100644 --- a/src/fountain.c +++ b/src/fountain.c @@ -292,7 +292,7 @@ drinkfountain(void) losehp(rnd(4), "unrefrigerated sip of juice", KILLED_BY_AN); break; } - losestr(rn1(4, 3)); + losestr(rn1(4, 3), "contaminated water", KILLED_BY); losehp(rnd(10), "contaminated water", KILLED_BY); exercise(A_CON, FALSE); break; diff --git a/src/mcastu.c b/src/mcastu.c index f83a38689..d8dc1c9e4 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -465,11 +465,7 @@ cast_wizard_spell(struct monst *mtmp, int dmg, int spellnum) dmg = mtmp->m_lev - 6; if (Half_spell_damage) dmg = (dmg + 1) / 2; - losestr(rnd(dmg)); - if (u.uhp < 1) - done_in_by(mtmp, DIED); - else if (Upolyd && u.mh < 1) - rehumanize(); + losestr(rnd(dmg), (const char *) 0, 0); } dmg = 0; break; diff --git a/src/spell.c b/src/spell.c index f10c86a4c..d72e42dde 100644 --- a/src/spell.c +++ b/src/spell.c @@ -152,7 +152,8 @@ cursed_book(struct obj* bp) /* temp disable in_use; death should not destroy the book */ was_in_use = bp->in_use; bp->in_use = FALSE; - losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3)); + losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3), + "contact-poisoned spellbook", KILLED_BY_AN); losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook", KILLED_BY_AN); bp->in_use = was_in_use; From 02367077bde9af6f62164142f6f03ef6788826da Mon Sep 17 00:00:00 2001 From: Michael Meyer Date: Tue, 4 Oct 2022 17:52:01 -0400 Subject: [PATCH 4/5] Use function for combined str/hp loss from poison Since losestr and losehp calls go together most of the time, this feels like it probably makes more sense than repeating the killer name/format twice in a row all over the place. --- include/extern.h | 1 + src/attrib.c | 8 ++++++++ src/eat.c | 10 ++++------ src/fountain.c | 4 ++-- src/spell.c | 7 +++---- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/extern.h b/include/extern.h index 2f7e2b3c8..85c0afd09 100644 --- a/include/extern.h +++ b/include/extern.h @@ -115,6 +115,7 @@ extern struct obj *has_magic_key(struct monst *); extern boolean adjattrib(int, int, int); extern void gainstr(struct obj *, int, boolean); extern void losestr(int, const char *, schar); +extern void poison_strdmg(int, int, const char *, schar); extern void poisontell(int, boolean); extern void poisoned(const char *, int, const char *, int, boolean); extern void change_luck(schar); diff --git a/src/attrib.c b/src/attrib.c index e74abd3e2..8dd57b291 100644 --- a/src/attrib.c +++ b/src/attrib.c @@ -241,6 +241,14 @@ losestr(int num, const char *knam, schar k_format) (void) adjattrib(A_STR, -num, 1); } +/* combined strength loss and damage from some poisons */ +void +poison_strdmg(int strloss, int dmg, const char *knam, schar k_format) +{ + losestr(strloss, knam, k_format); + losehp(dmg, knam, k_format); +} + static const struct poison_effect_message { void (*delivery_func)(const char *, ...); const char *effect_msg; diff --git a/src/eat.c b/src/eat.c index c7108eafa..bfd567006 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1815,9 +1815,9 @@ eatcorpse(struct obj *otmp) tp++; pline("Ecch - that must have been poisonous!"); if (!Poison_resistance) { - const char *knam = !glob ? "poisonous corpse" : "poisonous glob"; - losestr(rnd(4), knam, KILLED_BY_AN); - losehp(rnd(15), knam, KILLED_BY_AN); + poison_strdmg(rnd(4), rnd(15), + !glob ? "poisonous corpse" : "poisonous glob", + KILLED_BY_AN); } else You("seem unaffected by the poison."); @@ -2753,9 +2753,7 @@ doeat(void) if (otmp->oclass == WEAPON_CLASS && otmp->opoisoned) { pline("Ecch - that must have been poisonous!"); if (!Poison_resistance) { - const char *knam = xname(otmp); - losestr(rnd(4), knam, KILLED_BY_AN); - losehp(rnd(15), knam, KILLED_BY_AN); + poison_strdmg(rnd(4), rnd(15), xname(otmp), KILLED_BY_AN); } else You("seem unaffected by the poison."); } else if (!nodelicious) { diff --git a/src/fountain.c b/src/fountain.c index 8791f39b3..d965b0a0a 100644 --- a/src/fountain.c +++ b/src/fountain.c @@ -292,8 +292,8 @@ drinkfountain(void) losehp(rnd(4), "unrefrigerated sip of juice", KILLED_BY_AN); break; } - losestr(rn1(4, 3), "contaminated water", KILLED_BY); - losehp(rnd(10), "contaminated water", KILLED_BY); + poison_strdmg(rn1(4, 3), rnd(10), "contaminated water", + KILLED_BY); exercise(A_CON, FALSE); break; case 22: /* Fountain of snakes! */ diff --git a/src/spell.c b/src/spell.c index d72e42dde..248ec0685 100644 --- a/src/spell.c +++ b/src/spell.c @@ -152,10 +152,9 @@ cursed_book(struct obj* bp) /* temp disable in_use; death should not destroy the book */ was_in_use = bp->in_use; bp->in_use = FALSE; - losestr(Poison_resistance ? rn1(2, 1) : rn1(4, 3), - "contact-poisoned spellbook", KILLED_BY_AN); - losehp(rnd(Poison_resistance ? 6 : 10), "contact-poisoned spellbook", - KILLED_BY_AN); + poison_strdmg(Poison_resistance ? rn1(2, 1) : rn1(4, 3), + rnd(Poison_resistance ? 6 : 10), + "contact-poisoned spellbook", KILLED_BY_AN); bp->in_use = was_in_use; break; case 6: From b80cf6138c4efc8c5a4d52159e5e4ea629085b1a Mon Sep 17 00:00:00 2001 From: Michael Meyer Date: Tue, 4 Oct 2022 18:27:40 -0400 Subject: [PATCH 5/5] Don't hardcode min Str in losestr Min Str is typically 3 no matter the hero's race, but could be higher (at least in theory?). Using ATTRMIN makes losestr respect the same minimum as other kinds of attribute loss (I'm operating under the assumption that this wasn't an intentional move to fix the minimum at 3 regardless of other factors). --- src/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attrib.c b/src/attrib.c index 8dd57b291..7684c057c 100644 --- a/src/attrib.c +++ b/src/attrib.c @@ -222,7 +222,7 @@ losestr(int num, const char *knam, schar k_format) k_format = KILLED_BY; } - while (ustr < 3) { + while (ustr < ATTRMIN(A_STR)) { ++ustr; --num; losehp(6, knam, k_format);