Enable more ways to specify monster inventory in special levels

This originated with a bug in NerfHack in which the developer specified
an inventory for a quest nemesis, but neglected to include the Bell of
Opening in it. Since monsters' inventory contents from makemon() were
tossed out completely, this caused a situation where the Bell was
deleted and the game was unwinnable. The first part of this change is
guarding against that by adding mdrop_special_objs before discarding the
inventory. This does create a possibility where if the programmer *does*
specify a nemesis get the Bell item in their inventory, while neglecting
to remove its special case generation in makemon.c, it would generate
twice - but two Bells is better than none.

Working on that fix led me to think about a limitation of the current
sp_lev.c behavior. You could either have a monster generate with its
species-typical inventory by not specifying an inventory for it, or you
could have it generate with custom inventory but then have to use that
to clumsily reproduce the normal inventory's complex chances and
conditionals in mongets(). So the remainder of this commit implements
another flag for des.monster(), keep_default_invent, that allows for
more flexibility in two ways:

1. When des.monster() contains an inventory function and
   keep_default_invent is true, the monster will retain everything it
   gets from makemon() and the objects in the inventory function are in
   ADDITION to those. This is useful for augmenting a monster's default
   kit with something to make them more threatening, or just more loot.
2. When des.monster contains no inventory function and
   keep_default_invent is false, the monster will get NO inventory even
   if its species is normally supposed to. I'm not sure where exactly
   this would be used, but it doesn't hurt to have it available.

When keep_default_invent is not specified at all, the behavior remains
the same as it is now - if inventory is provided, default items are
discarded, and if not, they are kept.
This commit is contained in:
copperwater
2025-01-01 19:56:49 -05:00
committed by Pasi Kallinen
parent 171d48c881
commit 2d4f9893ad
3 changed files with 33 additions and 6 deletions

View File

@@ -794,7 +794,8 @@ The hash parameter accepts the following keys:
| ignorewater | boolean | ignore water when choosing location for the monster
| countbirth | boolean | do we count this monster as generated
| appear_as | string | monster can appear as object, monster, or terrain. Add "obj:", "mon:", or "ter:" prefix to the value. |
| inventory | function | objects generated in the function are given to the monster
| inventory | function | objects generated in the function are given to the monster (any random inventory it gets is discarded unless keep_default_invent is true)
| keep_default_invent | boolean | if inventory is specified and this is true, those items are in addition to random inventory for this species; if inventory is not specified and this is false, monster gets no starting inventory
|===
Example:

View File

@@ -74,6 +74,11 @@ enum lvlinit_types {
#define NO_LOC_WARN 0x20 /* no complaints and set x & y to -1, if no loc */
#define SPACELOC 0x40 /* like DRY, but accepts furniture too */
/* has_invent flags */
#define NO_INVENT 0 /* monster doesn't get any invent */
#define CUSTOM_INVENT 0x01 /* monster gets items specified in lua */
#define DEFAULT_INVENT 0x02 /* monster gets items from makemon() */
#define SP_COORD_X(l) (l & 0xff)
#define SP_COORD_Y(l) ((l >> 16) & 0xff)
#define SP_COORD_PACK(x, y) (((x) & 0xff) + (((y) & 0xff) << 16))

View File

@@ -2165,8 +2165,14 @@ create_monster(monster *m, struct mkroom *croom)
if (vampshifted(mtmp) && m->appear != M_AP_MONSTER)
(void) newcham(mtmp, &mons[mtmp->cham], NO_NC_FLAGS);
}
if (m->has_invent) {
if (!(m->has_invent & DEFAULT_INVENT)) {
/* guard against someone accidentally specifying e.g. quest nemesis
* with custom inventory that lacks Bell or quest artifact but
* forgetting to flag them as receiving their default inventory */
mdrop_special_objs(mtmp);
discard_minvent(mtmp, TRUE);
}
if (m->has_invent & CUSTOM_INVENT) {
invent_carrying_monster = mtmp;
}
}
@@ -3217,7 +3223,7 @@ lspo_monster(lua_State *L)
tmpmons.stunned = 0;
tmpmons.confused = 0;
tmpmons.seentraps = 0;
tmpmons.has_invent = 0;
tmpmons.has_invent = DEFAULT_INVENT;
tmpmons.waiting = 0;
tmpmons.mm_flags = NO_MM_FLAGS;
@@ -3265,6 +3271,7 @@ lspo_monster(lua_State *L)
: (mgend == MALE) ? MALE : rn2(2);
}
} else {
int keep_default_invent = -1; /* -1 = unspecified */
lcheck_param_table(L);
tmpmons.peaceful = get_table_boolean_opt(L, "peaceful", BOOL_RANDOM);
@@ -3285,7 +3292,8 @@ lspo_monster(lua_State *L)
tmpmons.confused = get_table_boolean_opt(L, "confused", FALSE);
tmpmons.waiting = get_table_boolean_opt(L, "waiting", FALSE);
tmpmons.seentraps = 0; /* TODO: list of trap names to bitfield */
tmpmons.has_invent = 0;
keep_default_invent =
get_table_boolean_opt(L, "keep_default_invent", -1);
if (!get_table_boolean_opt(L, "tail", TRUE))
tmpmons.mm_flags |= MM_NOTAIL;
@@ -3334,7 +3342,19 @@ lspo_monster(lua_State *L)
lua_getfield(L, 1, "inventory");
if (!lua_isnil(L, -1)) {
tmpmons.has_invent = 1;
/* overwrite DEFAULT_INVENT - most times inventory is specified,
* the monster should not get its species' default inventory. Only
* provide it if explicitly requested. */
tmpmons.has_invent = CUSTOM_INVENT;
if (keep_default_invent == TRUE)
tmpmons.has_invent |= DEFAULT_INVENT;
}
else {
/* if keep_default_invent was not specified (-1), keep has_invent as
* DEFAULT_INVENT and provide the species' default inventory.
* But if it was explicitly set to false, provide *no* inventory. */
if (keep_default_invent == FALSE)
tmpmons.has_invent = NO_INVENT;
}
}
@@ -3348,7 +3368,8 @@ lspo_monster(lua_State *L)
create_monster(&tmpmons, gc.coder->croom);
if (tmpmons.has_invent && lua_type(L, -1) == LUA_TFUNCTION) {
if ((tmpmons.has_invent & CUSTOM_INVENT)
&& lua_type(L, -1) == LUA_TFUNCTION) {
lua_remove(L, -2);
nhl_pcall_handle(L, 0, 0, "lspo_monster", NHLpa_panic);
spo_end_moninvent();