From ce7b7710d85968f08c1097a6ae33edd5920202d0 Mon Sep 17 00:00:00 2001 From: nhmall Date: Fri, 28 Feb 2025 12:40:34 -0500 Subject: [PATCH] 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. --- src/makemon.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/makemon.c b/src/makemon.c index d7eff9f71..2de8003ae 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -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