fix #K669 - 'nasty' monster summoning

Report complained about multiple Archons causing his character to
be swarmed by monsters on the Plane of Fire.  I don't think that
the behavior has changed significantly from how it worked in 3.4.3.
Nobody can summon an Archon directly because they're excluded from
the nasties[] list.  But whenever summoning picks a genocided
'nasty', the result gets replaced by random monster of appropriate
difficulty for the level (which could be an Archon for a high level
character in the endgame).  [Note that that won't pick an Archon
in Gehennom or at arch-lich outside of there because the random
monster creation honors the only-in-hell and never-in-hell flags;
picking from the nasties[] list doesn't.]

This prevents that for any creature (except arch-lich or the Wizard)
casting the summon nasties spell.  If a replacement creature is a
spellcaster it now has to have lower difficulty than the summoner.
If not, it will be discarded even though its difficulty is classified
as appropriate.  So to summon an Archon, the summoner has to have
higher difficulty than an Archon; arch-lich and the Wizard are the
only ones meeting that criterium.  When summoner is an arch-lich,
it can't summon another arch-lich (since that wouldn't have lower
difficulty than the summoner) and can summon (via replacement for
genocided type, and only if outside of Gehennom) at most one Archon.
When summoner is the Wizard, he could summon an arch-lich (when in
Gehennom; demoted to master lich elsewhere--see below) or an Archon
(outside Gehennom only), but at most one per summoning.

For post-Wizard harassment, which effectively has infinite
difficulty level, it could still happen.  However, each instance of
harassment is only allowed to create at most one Archon or arch-lich
now, so chain summoning should be lessoned.  Also if it tries to
pick an arch-lich when outside of Gehennom it will switch to master
lich instead (which won't be allowed to summon an Archon or an arch-
lich or even another master lich).

(The monmove.c bit is unrelated, just some comment formatting that
I had laying around that got mixed in.)
This commit is contained in:
PatR
2020-03-27 19:05:52 -07:00
parent 0ac3329289
commit 3eed500886
8 changed files with 143 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 wizard.c $NHDT-Date: 1561336025 2019/06/24 00:27:05 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.56 $ */
/* NetHack 3.6 wizard.c $NHDT-Date: 1585361057 2020/03/28 02:04:17 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.64 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2016. */
/* NetHack may be freely redistributed. See license for details. */
@@ -41,7 +41,7 @@ static NEARDATA const int nasties[] = {
PM_SILVER_DRAGON, PM_ORANGE_DRAGON, PM_GREEN_DRAGON,
PM_YELLOW_DRAGON, PM_GUARDIAN_NAGA, PM_FIRE_GIANT,
PM_ALEAX, PM_COUATL, PM_HORNED_DEVIL, PM_BARBED_DEVIL,
/* (titans, ki-rin, and golden nagas are suitably nasty, but
/* (Archons, titans, ki-rin, and golden nagas are suitably nasty, but
they're summoners so would aggravate excessive summoning) */
};
@@ -512,9 +512,10 @@ clonewiz()
/* also used by newcham() */
int
pick_nasty()
pick_nasty(difcap)
int difcap; /* if non-zero, try to make difficulty be lower than this */
{
int res = nasties[rn2(SIZE(nasties))];
int alt, res = nasties[rn2(SIZE(nasties))];
/* To do? Possibly should filter for appropriate forms when
* in the elemental planes or surrounded by water or lava.
@@ -526,6 +527,33 @@ pick_nasty()
&& !('A' <= mons[res].mlet && mons[res].mlet <= 'Z'))
res = nasties[rn2(SIZE(nasties))];
/* if genocided or too difficult or out of place, try a substitute
when a suitable one exists
arch-lich -> master lich,
master mind flayer -> mind flayer,
but the substitutes are likely to be genocided too */
alt = res;
if ((g.mvitals[res].mvflags & G_GENOD) != 0
|| (difcap > 0 && mons[res].difficulty >= difcap)
/* note: nasty() -> makemon() ignores G_HELL|G_NOHELL;
arch-lich and master lich are both flagged as hell-only;
this filtering demotes arch-lich to master lich when
outside of Gehennom (unless the latter has been genocided) */
|| (mons[res].geno & (Inhell ? G_NOHELL : G_HELL)) != 0)
alt = big_to_little(res);
if (alt != res && (g.mvitals[alt].mvflags & G_GENOD) == 0) {
const char *mname = mons[alt].mname,
*lastspace = rindex(mname, ' ');
/* only non-juveniles can become alternate choice */
if (strncmp(mname, "baby ", 5)
&& (!lastspace
|| (strcmp(lastspace, " hatchling")
&& strcmp(lastspace, " pup")
&& strcmp(lastspace, " cub"))))
res = alt;
}
return res;
}
@@ -540,11 +568,10 @@ int
nasty(summoner)
struct monst *summoner;
{
register struct monst *mtmp;
register int i, j;
int castalign = (summoner ? sgn(summoner->data->maligntyp) : 0);
struct monst *mtmp;
coord bypos;
int count, census, tmp, makeindex, s_cls, m_cls;
int i, j, count, census, tmp, makeindex,
s_cls, m_cls, difcap, trylimit, castalign;
#define MAXNASTIES 10 /* more than this can be created */
@@ -558,12 +585,14 @@ struct monst *summoner;
} else {
count = 0;
s_cls = summoner ? summoner->data->mlet : 0;
difcap = summoner ? summoner->data->difficulty : 0; /* spellcasters */
castalign = summoner ? sgn(summoner->data->maligntyp) : 0;
tmp = (u.ulevel > 3) ? u.ulevel / 3 : 1;
/* if we don't have a casting monster, nasties appear around hero,
otherwise they'll appear around spot summoner thinks she's at */
bypos.x = u.ux;
bypos.y = u.uy;
for (i = rnd(tmp); i > 0 && count < MAXNASTIES; --i)
for (i = rnd(tmp); i > 0 && count < MAXNASTIES; --i) {
/* Of the 42 nasties[], 10 are lawful, 14 are chaotic,
* and 18 are neutral.
*
@@ -585,15 +614,16 @@ struct monst *summoner;
/* Don't create more spellcasters of the monsters' level or
* higher--avoids chain summoners filling up the level.
*/
trylimit = 10 + 1; /* 10 tries */
do {
makeindex = pick_nasty();
if (!--trylimit)
goto nextj; /* break this loop, continue outer one */
makeindex = pick_nasty(difcap);
m_cls = mons[makeindex].mlet;
} while (summoner
&& ((attacktype(&mons[makeindex], AT_MAGC)
&& mons[makeindex].difficulty
>= mons[summoner->mnum].difficulty)
|| (s_cls == S_DEMON && m_cls == S_ANGEL)
|| (s_cls == S_ANGEL && m_cls == S_DEMON)));
} while ((difcap > 0 && mons[makeindex].difficulty >= difcap
&& attacktype(&mons[makeindex], AT_MAGC))
|| (s_cls == S_DEMON && m_cls == S_ANGEL)
|| (s_cls == S_ANGEL && m_cls == S_DEMON));
/* do this after picking the monster to place */
if (summoner && !enexto(&bypos, summoner->mux, summoner->muy,
&mons[makeindex]))
@@ -604,18 +634,46 @@ struct monst *summoner;
NO_MM_FLAGS)) != 0) {
mtmp->msleeping = mtmp->mpeaceful = mtmp->mtame = 0;
set_malign(mtmp);
} else /* random monster to substitute for geno'd selection */
mtmp = makemon((struct permonst *) 0, bypos.x, bypos.y,
NO_MM_FLAGS);
} else {
/* random monster to substitute for geno'd selection;
unlike direct choice, not forced to be hostile [why?];
limit spellcasters to inhibit chain summoning */
if ((mtmp = makemon((struct permonst *) 0,
bypos.x, bypos.y,
NO_MM_FLAGS)) != 0) {
m_cls = mtmp->data->mlet;
if ((difcap > 0 && mtmp->data->difficulty >= difcap
&& attacktype(mtmp->data, AT_MAGC))
|| (s_cls == S_DEMON && m_cls == S_ANGEL)
|| (s_cls == S_ANGEL && m_cls == S_DEMON))
mtmp = unmakemon(mtmp, NO_MM_FLAGS); /* Null */
}
}
if (mtmp) {
/* create at most one arch-lich or Archon regardless
of who is doing the summoning (note: Archon is
not in nasties[] but could be chosen as random
replacement for a genocided selection) */
if (mtmp->data == &mons[PM_ARCH_LICH]
|| mtmp->data == &mons[PM_ARCHON]) {
tmp = min(mons[PM_ARCHON].difficulty, /* A:26 */
mons[PM_ARCH_LICH].difficulty); /* L:31 */
if (!difcap || difcap > tmp)
difcap = tmp; /* rest must be lower difficulty */
}
/* delay first use of spell or breath attack */
mtmp->mspec_used = rnd(4);
if (++count >= MAXNASTIES
|| mtmp->data->maligntyp == 0
|| sgn(mtmp->data->maligntyp) == castalign)
break;
}
}
nextj:
; /* empty; label must be followed by a statement */
} /* for j */
} /* for i */
}
if (count)