Remove requirement of object probs adding to 1000
When discussing the recent commit that removed makedefs -o from the build process, nhmall pointed out that a sanity check ensuring all objects within one class add up to 1000 probability had been removed as well. This requirement was a perennial thorn in the side for anyone doing anything that touches object probabilities, because allocating probability to something meant deciding what to take it away from, without a good way to evenly distribute that across all the other members of the object class. I had gotten around this in xNetHack by removing the sanity check and making mkobj() total up the probability within an object class and then using that instead of 1000. This commit takes a similar approach, but instead of inefficiently recalculating the sum every time mkobj() is called, it instead computes it at the start of the game or when restoring the save file and stores it in a global variable. This fixes a slight bias problem with rings - they are all supposed to be of equal probability, but there are 28 of them and 1000 is not evenly divisible by that, so the old formula made the later rings slightly more likely. Now instead of a 35/1000 or 36/1000 chance, they are all uniformly 1/28. (Internally they have a oc_prob of 1 now, not 0). Gems are also weird, because their oc_prob values change every level. This ought to have still worked without a change, because the arcane formula for assigning the probabilities would still end up with them adding to 1000. But I added in code to reset the total gem probability anyway; this may help make the formula less arcane in the future. There is still a sanity check against object classes having a nonzero number of objects but zero total probability, in which case an impossible will be thrown and every member of the class will be given equal probability. I also downgraded the "probtype error" panic in mkobj() to an impossible because it has a reasonable failure case - return the first item in that class.
This commit is contained in:
committed by
Pasi Kallinen
parent
ecee4e8610
commit
0e05c94400
@@ -1056,6 +1056,7 @@ struct instance_globals {
|
||||
|
||||
/* o_init.c */
|
||||
short disco[NUM_OBJECTS];
|
||||
short oclass_prob_totals[MAXOCLASSES];
|
||||
|
||||
/* objname.c */
|
||||
/* distantname used by distant_name() to pass extra information to
|
||||
|
||||
@@ -1741,6 +1741,7 @@ extern void consoletty_exit(void);
|
||||
/* ### o_init.c ### */
|
||||
|
||||
extern void init_objects(void);
|
||||
extern void init_oclass_probs(void);
|
||||
extern void obj_shuffle_range(int, int *, int *);
|
||||
extern int find_skates(void);
|
||||
extern boolean objdescr_is(struct obj *, const char *);
|
||||
|
||||
@@ -651,7 +651,7 @@ BOOTS("levitation boots", "snow boots",
|
||||
OBJECT(OBJ(name, stone), \
|
||||
BITS(0, 0, spec, 0, mgc, spec, 0, 0, 0, \
|
||||
HARDGEM(mohs), 0, P_NONE, metal), \
|
||||
power, RING_CLASS, 0, 0, 3, cost, 0, 0, 0, 0, 15, color,sn)
|
||||
power, RING_CLASS, 1, 0, 3, cost, 0, 0, 0, 0, 15, color,sn)
|
||||
RING("adornment", "wooden",
|
||||
ADORNED, 100, 1, 1, 2, WOOD, HI_WOOD, RIN_ADORNMENT),
|
||||
RING("gain strength", "granite",
|
||||
|
||||
@@ -528,6 +528,7 @@ const struct instance_globals g_init = {
|
||||
|
||||
/* o_init.c */
|
||||
DUMMY, /* disco */
|
||||
DUMMY, /* oclass_prob_totals */
|
||||
|
||||
/* objname.c */
|
||||
0, /* distantname */
|
||||
|
||||
@@ -206,7 +206,7 @@ mksobj_migr_to_species(
|
||||
struct obj *
|
||||
mkobj(int oclass, boolean artif)
|
||||
{
|
||||
int tprob, i, prob = rnd(1000);
|
||||
int tprob, i, prob;
|
||||
|
||||
if (oclass == RANDOM_CLASS) {
|
||||
const struct icp *iprobs = Is_rogue_level(&u.uz)
|
||||
@@ -223,13 +223,16 @@ mkobj(int oclass, boolean artif)
|
||||
i = rnd_class(g.bases[SPBOOK_CLASS], SPE_BLANK_PAPER);
|
||||
oclass = SPBOOK_CLASS; /* for sanity check below */
|
||||
} else {
|
||||
prob = rnd(g.oclass_prob_totals[oclass]);
|
||||
i = g.bases[oclass];
|
||||
while ((prob -= objects[i].oc_prob) > 0)
|
||||
++i;
|
||||
}
|
||||
|
||||
if (objects[i].oc_class != oclass || !OBJ_NAME(objects[i]))
|
||||
panic("probtype error, oclass=%d i=%d", (int) oclass, i);
|
||||
if (objects[i].oc_class != oclass || !OBJ_NAME(objects[i])) {
|
||||
impossible("probtype error, oclass=%d i=%d", (int) oclass, i);
|
||||
i = g.bases[oclass];
|
||||
}
|
||||
|
||||
return mksobj(i, TRUE, artif);
|
||||
}
|
||||
|
||||
49
src/o_init.c
49
src/o_init.c
@@ -42,7 +42,7 @@ shuffle_tiles(void)
|
||||
static void
|
||||
setgemprobs(d_level* dlev)
|
||||
{
|
||||
int j, first, lev;
|
||||
int j, first, lev, sum = 0;
|
||||
|
||||
if (dlev)
|
||||
lev = (ledger_no(dlev) > maxledgerno()) ? maxledgerno()
|
||||
@@ -62,6 +62,11 @@ setgemprobs(d_level* dlev)
|
||||
}
|
||||
for (j = first; j <= LAST_GEM; j++)
|
||||
objects[j].oc_prob = (171 + j - first) / (LAST_GEM + 1 - first);
|
||||
|
||||
/* recompute GEM_CLASS total oc_prob - including rocks/stones */
|
||||
for (j = g.bases[GEM_CLASS]; j < g.bases[GEM_CLASS + 1]; j++)
|
||||
sum += objects[j].oc_prob;
|
||||
g.oclass_prob_totals[GEM_CLASS] = sum;
|
||||
}
|
||||
|
||||
/* shuffle descriptions on objects o_low to o_high */
|
||||
@@ -106,7 +111,7 @@ shuffle(int o_low, int o_high, boolean domaterial)
|
||||
void
|
||||
init_objects(void)
|
||||
{
|
||||
int i, first, last, sum, prevoclass;
|
||||
int i, first, last, prevoclass;
|
||||
char oclass;
|
||||
#ifdef TEXTCOLOR
|
||||
#define COPY_OBJ_DESCR(o_dst, o_src) \
|
||||
@@ -167,17 +172,6 @@ init_objects(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
checkprob:
|
||||
sum = 0;
|
||||
for (i = first; i < last; i++)
|
||||
sum += objects[i].oc_prob;
|
||||
if (sum == 0) {
|
||||
for (i = first; i < last; i++)
|
||||
objects[i].oc_prob = (1000 + i - first) / (last - first);
|
||||
goto checkprob;
|
||||
}
|
||||
if (sum != 1000)
|
||||
error("init-prob error for class %d (%d%%)", oclass, sum);
|
||||
first = last;
|
||||
prevoclass = (int) oclass;
|
||||
}
|
||||
@@ -207,6 +201,8 @@ init_objects(void)
|
||||
objects[i].oc_name_known = nmkn ? 0 : 1;
|
||||
}
|
||||
}
|
||||
/* compute oclass_prob_totals */
|
||||
init_oclass_probs();
|
||||
|
||||
/* shuffle descriptions */
|
||||
shuffle_all();
|
||||
@@ -216,6 +212,33 @@ init_objects(void)
|
||||
objects[WAN_NOTHING].oc_dir = rn2(2) ? NODIR : IMMEDIATE;
|
||||
}
|
||||
|
||||
/* Compute the total probability of each object class.
|
||||
* Assumes g.bases[] has already been set. */
|
||||
void
|
||||
init_oclass_probs(void)
|
||||
{
|
||||
int i;
|
||||
short sum;
|
||||
int oclass;
|
||||
for (oclass = 0; oclass < MAXOCLASSES; ++oclass) {
|
||||
sum = 0;
|
||||
for (i = g.bases[oclass]; i < g.bases[oclass + 1]; ++i) {
|
||||
sum += objects[i].oc_prob;
|
||||
}
|
||||
if (sum <= 0 && oclass != ILLOBJ_CLASS
|
||||
&& g.bases[oclass] != g.bases[oclass + 1]) {
|
||||
impossible("zero or negative probability total for oclass %d",
|
||||
oclass);
|
||||
/* gracefully fail by setting all members of this class to 1 */
|
||||
for (i = g.bases[oclass]; i < g.bases[oclass + 1]; ++i) {
|
||||
objects[i].oc_prob = 1;
|
||||
sum++;
|
||||
}
|
||||
}
|
||||
g.oclass_prob_totals[oclass] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
/* retrieve the range of objects that otyp shares descriptions with */
|
||||
void
|
||||
obj_shuffle_range(
|
||||
|
||||
@@ -527,7 +527,7 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid)
|
||||
|
||||
if (nhfp->structlevel)
|
||||
mread(nhfp->fd, (genericptr_t) &uid, sizeof uid);
|
||||
|
||||
|
||||
if (SYSOPT_CHECK_SAVE_UID
|
||||
&& uid != (unsigned long) getuid()) { /* strange ... */
|
||||
/* for wizard mode, issue a reminder; for others, treat it
|
||||
@@ -582,7 +582,7 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid)
|
||||
if (nhfp->structlevel)
|
||||
mread(nhfp->fd, (genericptr_t) &u, sizeof(struct you));
|
||||
g.youmonst.cham = u.mcham;
|
||||
|
||||
|
||||
if (nhfp->structlevel)
|
||||
mread(nhfp->fd, (genericptr_t) timebuf, 14);
|
||||
timebuf[14] = '\0';
|
||||
@@ -692,7 +692,7 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid)
|
||||
}
|
||||
freefruitchn(g.ffruit); /* clean up fruit(s) made by initoptions() */
|
||||
g.ffruit = loadfruitchn(nhfp);
|
||||
|
||||
|
||||
restnames(nhfp);
|
||||
restore_waterlevel(nhfp);
|
||||
restore_msghistory(nhfp);
|
||||
@@ -857,6 +857,7 @@ dorecover(NHFILE* nhfp)
|
||||
substitute_tiles(&u.uz);
|
||||
#endif
|
||||
max_rank_sz(); /* to recompute g.mrank_sz (botl.c) */
|
||||
init_oclass_probs(); /* recompute g.oclass_prob_totals[] */
|
||||
/* take care of iron ball & chain */
|
||||
for (otmp = fobj; otmp; otmp = otmp->nobj)
|
||||
if (otmp->owornmask)
|
||||
@@ -1078,7 +1079,7 @@ getlev(NHFILE* nhfp, int pid, xchar lev)
|
||||
g.doorindex = g.rooms[g.nroom - 1].fdoor + g.rooms[g.nroom - 1].doorct;
|
||||
else
|
||||
g.doorindex = 0;
|
||||
|
||||
|
||||
restore_timers(nhfp, RANGE_LEVEL, elapsed);
|
||||
restore_light_sources(nhfp);
|
||||
fmon = restmonchn(nhfp);
|
||||
|
||||
Reference in New Issue
Block a user