Buff Grimtooth with poison

Grimtooth is now permanently poisoned, protects the wielder from
poison, and can be invoked to throw poison.

Permapoison code comes from xNetHack by copperwater <aosdict@gmail.com>.
This commit is contained in:
Pasi Kallinen
2025-04-12 19:23:20 +03:00
parent 48d1b8617e
commit 8f7258dc35
10 changed files with 96 additions and 27 deletions

View File

@@ -1507,6 +1507,7 @@ since introduction in 3.1.0, the definition for Mitre of Holiness has specified
if a tree and a boulder or statue were at the same location, applying an axe
would break the boulder or statue rather than chop the tree
avoid premapping outside Sokoban map to prevent showing stone glyphs
Grimtooth is permanently poisoned, protects from poison, and can fling poison
Fixes to 3.7.0-x General Problems Exposed Via git Repository

View File

@@ -67,6 +67,7 @@ enum invoke_prop_types {
ENLIGHTENING,
CREATE_AMMO,
BANISH,
FLING_POISON,
BLINDING_RAY
};

View File

@@ -43,6 +43,7 @@ static const char *const artifact_names[] = {
#define FIRE(a,b) {0,AD_FIRE,a,b}
#define ELEC(a,b) {0,AD_ELEC,a,b} /* electrical shock */
#define STUN(a,b) {0,AD_STUN,a,b} /* magical attack */
#define POIS(a,b) {0,AD_DRST,a,b} /* poison */
/* clang-format on */
static NEARDATA struct artifact artilist[] = {
@@ -120,9 +121,9 @@ static NEARDATA struct artifact artilist[] = {
* (handled as special case in spec_dbon()).
*/
A("Grimtooth", ORCISH_DAGGER, (SPFX_RESTR | SPFX_WARN | SPFX_DFLAG2),
0, M2_ELF, PHYS(2, 6), NO_DFNS,
NO_CARY, 0, A_CHAOTIC, NON_PM, PM_ORC,
0, 5, 300L, CLR_RED, GRIMTOOTH),
0, M2_ELF, PHYS(2, 6), POIS(0,0),
NO_CARY, FLING_POISON, A_CHAOTIC, NON_PM, PM_ORC,
0, 5, 1200L, CLR_RED, GRIMTOOTH),
/*
* Orcrist and Sting have same alignment as elves.
*

View File

@@ -183,6 +183,7 @@ extern void mkot_trap_warn(void);
extern boolean is_magic_key(struct monst *, struct obj *);
extern struct obj *has_magic_key(struct monst *);
extern boolean is_art(struct obj *, int);
extern boolean permapoisoned(struct obj *);
/* ### attrib.c ### */

View File

@@ -254,10 +254,11 @@ struct obj {
(otmp->oclass == WEAPON_CLASS \
&& objects[otmp->otyp].oc_skill >= -P_SHURIKEN \
&& objects[otmp->otyp].oc_skill <= -P_BOW)
#define is_poisonable(otmp) \
(otmp->oclass == WEAPON_CLASS \
&& objects[otmp->otyp].oc_skill >= -P_SHURIKEN \
&& objects[otmp->otyp].oc_skill <= -P_BOW)
#define is_poisonable(otmp) \
((otmp->oclass == WEAPON_CLASS \
&& objects[otmp->otyp].oc_skill >= -P_SHURIKEN \
&& objects[otmp->otyp].oc_skill <= -P_BOW) \
|| permapoisoned(otmp))
#define uslinging() (uwep && objects[uwep->otyp].oc_skill == P_SLING)
/* 'is_quest_artifact()' only applies to the current role's artifact */
#define any_quest_artifact(o) ((o)->oartifact >= ART_ORB_OF_DETECTION)

View File

@@ -283,6 +283,8 @@ mk_artifact(
otmp = 0;
} /* otherwise, otmp has not changed; just fallthrough to return it */
}
if (permapoisoned(otmp))
otmp->opoisoned = 1;
return otmp;
}
@@ -2009,6 +2011,19 @@ arti_invoke(struct obj *obj)
}
break;
}
case FLING_POISON:
if (getdir((char *) 0)) {
int venom = rn2(2) ? BLINDING_VENOM : ACID_VENOM;
struct obj *otmp = mksobj(venom, TRUE, FALSE);
otmp->spe = 1; /* the poison is yours */
throwit(otmp, 0L, FALSE, (struct obj *) 0);
} else {
/* no direction picked */
pline("%s", Never_mind);
obj->age = svm.moves;
}
break;
case BLINDING_RAY:
if (getdir((char *) 0)) {
if (u.dx || u.dy) {
@@ -2699,4 +2714,11 @@ get_artifact(struct obj *obj)
}
return &artilist[ART_NONARTIFACT];
}
/* is object permanently poisoned? (currently only Grimtooth) */
boolean
permapoisoned(struct obj *obj)
{
return (obj && is_art(obj, ART_GRIMTOOTH));
}
/*artifact.c*/

View File

@@ -1160,6 +1160,8 @@ mksobj_init(struct obj **obj, boolean artif)
}
mkobj_erosions(otmp);
if (permapoisoned(otmp))
otmp->opoisoned = 1;
}
/* mksobj(): create a specific type of object; result is always non-Null */

View File

@@ -5338,6 +5338,9 @@ readobjnam(char *bp, struct obj *no_wish)
}
}
if (permapoisoned(d.otmp))
d.otmp->opoisoned = 1;
/* more wishing abuse: don't allow wishing for certain artifacts */
/* and make them pay; charge them for the wish anyway! */
if ((is_quest_artifact(d.otmp)

View File

@@ -2595,9 +2595,10 @@ potion_dip(struct obj *obj, struct obj *potion)
obj->opoisoned = TRUE;
poof(potion);
return ECMD_TIME;
} else if (obj->opoisoned && (potion->otyp == POT_HEALING
|| potion->otyp == POT_EXTRA_HEALING
|| potion->otyp == POT_FULL_HEALING)) {
} else if (obj->opoisoned && !permapoisoned(obj)
&& (potion->otyp == POT_HEALING
|| potion->otyp == POT_EXTRA_HEALING
|| potion->otyp == POT_FULL_HEALING)) {
pline("A coating wears off %s.", the(xname(obj)));
obj->opoisoned = 0;
poof(potion);

View File

@@ -13,6 +13,8 @@ staticfn boolean mhitm_mgc_atk_negated(struct monst *, struct monst *,
staticfn boolean known_hitum(struct monst *, struct obj *, int *, int, int,
struct attack *, int) NONNULLARG13;
staticfn boolean theft_petrifies(struct obj *) NONNULLARG1;
staticfn void mhitm_really_poison(struct monst *, struct attack *,
struct monst *, struct mhitm_data *);
staticfn void steal_it(struct monst *, struct attack *) NONNULLARG1;
/* hitum_cleave() has contradictory information. There's a comment
* beside the 1st arg 'target' stating non-null, but later on there
@@ -1036,6 +1038,9 @@ hmon_hitmon_weapon_melee(
if (obj->opoisoned && is_poisonable(obj))
hmd->ispoisoned = TRUE;
}
/* permapoisoned is non-ammo/missile, limit the poison */
if (permapoisoned(obj) && hmd->dieroll <= 5)
hmd->ispoisoned = TRUE;
}
staticfn void
@@ -1484,7 +1489,7 @@ hmon_hitmon_poison(
You_feel("like an evil coward for using a poisoned weapon.");
adjalign(-1);
}
if (!rn2(nopoison)) {
if (!permapoisoned(obj) && !rn2(nopoison)) {
/* remove poison now in case obj ends up in a bones file */
obj->opoisoned = FALSE;
/* defer "obj is no longer poisoned" until after hit message */
@@ -3026,6 +3031,29 @@ mhitm_ad_curs(
}
}
/* Helper for mhitm_ad_drst(), containing some code that is also called from
* mhitm_ad_phys (for poisoned weapons) and shouldn't be subject to magic
* cancellation or a 1/8 chance roll.
* In this specific case, the "mhitm" in the name ACTUALLY means just that -
* this should be called only for monster versus monster situations. */
staticfn void
mhitm_really_poison(struct monst *magr, struct attack *mattk,
struct monst *mdef, struct mhitm_data *mhm)
{
if (gv.vis && canspotmon(magr))
pline("%s %s was poisoned!", s_suffix(Monnam(magr)),
mpoisons_subj(magr, mattk));
if (resists_poison(mdef)) {
if (gv.vis && canspotmon(mdef) && canspotmon(magr))
pline_The("poison doesn't seem to affect %s.",
mon_nam(mdef));
} else {
mhm->damage += rn1(10, 6);
if (mhm->damage >= mdef->mhp && gv.vis && canspotmon(mdef))
pline_The("poison was deadly...");
}
}
void
mhitm_ad_drst(
struct monst *magr, struct attack *mattk,
@@ -3067,22 +3095,7 @@ mhitm_ad_drst(
} else {
/* mhitm */
if (!negated && !rn2(8)) {
if (gv.vis && canspotmon(magr))
pline("%s %s was poisoned!", s_suffix(Monnam(magr)),
mpoisons_subj(magr, mattk));
if (resists_poison(mdef)) {
if (gv.vis && canspotmon(mdef) && canspotmon(magr))
pline_The("poison doesn't seem to affect %s.",
mon_nam(mdef));
} else {
if (rn2(10)) {
mhm->damage += rn1(10, 6);
} else {
if (gv.vis && canspotmon(mdef))
pline_The("poison was deadly...");
mhm->damage = mdef->mhp;
}
}
mhitm_really_poison(magr, mattk, mdef, mhm);
}
}
}
@@ -3957,6 +3970,7 @@ mhitm_ad_phys(
if (mattk->aatyp == AT_WEAP && otmp) {
struct obj *marmg;
int tmp;
boolean was_poisoned = (otmp->opoisoned || permapoisoned(otmp));
if (otmp->otyp == CORPSE
&& touch_petrifies(&mons[otmp->corpsenm])) {
@@ -4015,6 +4029,21 @@ mhitm_ad_phys(
You("divide as %s hits you!", mon_nam(magr));
}
rustm(&gy.youmonst, otmp);
if (was_poisoned && gm.mhitu_dieroll <= 5) {
char buf[BUFSZ];
/* similar to mhitm_really_poison, but we don't use the
* exact same values, nor do we want the same 1/8 chance of
* the poison taking (use 1/4, same as in the mhitm case). */
Sprintf(buf, "%s %s", s_suffix(Monnam(magr)),
mpoisons_subj(magr, mattk));
/* arbitrary, but most poison sources in the game are
* strength-based. With hpdamchance = 10, HP damage occurs
* 1/2 of the time and it will hit Str the rest of the time.
* (This is the same as poisoned ammo.) */
poisoned(buf, A_STR, pmname(magr->data, Mgender(magr)),
10, FALSE);
}
} else if (mattk->aatyp != AT_TUCH || mhm->damage != 0
|| magr != u.ustuck) {
hitmsg(magr, mattk);
@@ -4077,6 +4106,13 @@ mhitm_ad_phys(
}
if (mhm->damage)
rustm(mdef, mwep);
if ((mwep->opoisoned || permapoisoned(mwep)) && !rn2(4)) {
/* 1/4 chance of weapon poison applying is the same as in
* uhitm and mhitu cases. But since we don't need to call
* any special functions or go through tangled hmon_hitmon
* code, we can just jump straight to the poisoning. */
mhitm_really_poison(magr, mattk, mdef, mhm);
}
} else if (pa == &mons[PM_PURPLE_WORM] && pd == &mons[PM_SHRIEKER]) {
/* hack to enhance mm_aggression(); we don't want purple
worm's bite attack to kill a shrieker because then it