a fix for issue #1386 - impossible to gen ';' mon

Bug description of #1386 by @copperwater on GitHub:

  "When generating a random monster from a class using des.monster(),
  the G_NOGEN in their statblock is suppressed, but because every monster
  of this class has frequency 0, none of them are actually eligible to get
  picked. mkclass ends up returning a null pointer and create_monster has
  to pick a random monster instead.

  This affects the following levels (all the ones that use random sea monsters):

  Healer quest start
  Healer quest locate
  Plane of Water (difficult to notice, since it has lots of specific sea monsters and only 5 random ones)

  This can be pretty easily viewed by going to the Healer quest start and
  detecting monsters: there is a shark and a giant eel, which are
  specifically defined, but the remaining random sea monster that should
  be there is absent."

Add a tracking array mclass_maxf[MAXMCLASSES] (about 61 entries, the
first not being used), and fill it one time in init_mongen_order() with
the maximum frequency value seen of any monster in that class.

Any mclass_maxf[] entry of zero represents that entire class of monsters
having no positive frequency value.

Detect that in mkclass_aligned(), and use it to work around the situation
to produce the monster being sought by the Lua level description file.
This commit is contained in:
nhmall
2025-02-28 12:40:34 -05:00
parent d56b0a3b7a
commit ce7b7710d8

View File

@@ -1747,6 +1747,7 @@ mk_gen_ok(int mndx, unsigned mvflagsmask, unsigned genomask)
/* monsters in order by mlet & difficulty for mkclass() */
static int mongen_order[NUMMONS];
static xint8 mclass_maxf[MAXMCLASSES];
static boolean mongen_order_init = FALSE;
staticfn int QSORTCALLBACK
@@ -1796,15 +1797,18 @@ check_mongen_order(void)
staticfn void
init_mongen_order(void)
{
int i;
int i, mlet;
if (mongen_order_init)
return;
mongen_order_init = TRUE;
for (i = LOW_PM; i < NUMMONS; i++)
for (i = LOW_PM; i < NUMMONS; i++) {
mongen_order[i] = i;
mlet = mons[i].mlet;
if ((xint8) (mons[i].geno & G_FREQ) > mclass_maxf[mlet])
mclass_maxf[mlet] = (xint8) (mons[i].geno & G_FREQ);
}
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
check_mongen_order();
#endif
@@ -1836,9 +1840,11 @@ dump_mongen(void)
Snprintf(nmbuf, sizeof nmbuf, "PM_%s%s",
monsdump[MONSi(i)].nm,
(i == SPECIAL_PM - 1) ? "" : ",");
raw_printf(" %*s /* %c seq=%3d, idx=%3d, sym='%c', diff=%2d %s */",
raw_printf(" %*s /* %c seq=%3d, idx=%3d, sym='%c', diff=%2d, freq=%2d[%d] %s */",
-nmwidth, nmbuf, (i == MONSi(i)) ? ' ' : '.', i, MONSi(i),
mlet, (int) mons[MONSi(i)].difficulty,
(int) (mons[MONSi(i)].geno & G_FREQ),
(int) mclass_maxf[(int) mons[MONSi(i)].mlet],
(special == (G_NOGEN | G_UNIQ)) ? "(G_NOGEN | G_UNIQ)"
: (special == G_NOGEN) ? "(G_NOGEN)"
: (special == G_UNIQ) ? "(G_UNIQ)"
@@ -1869,6 +1875,7 @@ mkclass_aligned(char class, int spc, /* special mons[].geno handling */
int k, nums[SPECIAL_PM + 1]; /* +1: insurance for final return value */
int maxmlev, gehennom = Inhell != 0;
unsigned mv_mask, gn_mask;
boolean zero_freq_for_entire_class;
(void) memset((genericptr_t) nums, 0, sizeof nums);
maxmlev = level_difficulty() >> 1;
@@ -1878,6 +1885,8 @@ mkclass_aligned(char class, int spc, /* special mons[].geno handling */
}
init_mongen_order();
/* the following must come after init_mongen_order() */
zero_freq_for_entire_class = (mclass_maxf[(int) class] == 0);
/* Assumption #1: monsters of a given class are contiguous in the
* mons[] array. Player monsters and quest denizens
@@ -1927,7 +1936,8 @@ mkclass_aligned(char class, int spc, /* special mons[].geno handling */
&& mons[MONSi(last)].difficulty > mons[MONSi(last - 1)].difficulty
&& rn2(2))
break;
if ((k = (mons[MONSi(last)].geno & G_FREQ)) > 0) {
if ((k = (mons[MONSi(last)].geno & G_FREQ)) > 0
|| (k = (zero_freq_for_entire_class ? 1 : 0)) > 0) {
/* skew towards lower value monsters at lower exp. levels
(this used to be done in the next loop, but that didn't
work well when multiple species had the same level and