Improve m_search_item

Previously when monster was interested to pick up an item,
the code went through the whole object chain, so going through
all the items on the level. This caused problems with some games,
for example where the player created thousands of meatballs
in separate stacks.

Changed the code so it now looks at the map locations inside
the search radius, and the stacks in those map locations,
skipping locations as early as possible.
This commit is contained in:
Pasi Kallinen
2023-04-03 20:53:16 +03:00
parent f1ac29d42f
commit 201ee8383e

View File

@@ -1100,19 +1100,22 @@ maybe_spin_web(struct monst *mtmp)
}
}
/* max distmin() distance for monster to look for items */
#define SQSRCHRADIUS 5
/* monster looks for items it wants nearby */
static boolean
m_search_items(struct monst *mtmp, coordxy *ggx, coordxy *ggy, schar *mmoved, int *appr)
{
register int minr = SQSRCHRADIUS; /* not too far away */
register struct obj *otmp;
register coordxy xx, yy;
coordxy oomx, oomy, lmx, lmy;
coordxy hmx, hmy, lmx, lmy;
struct trap *ttmp;
coordxy omx = mtmp->mx, omy = mtmp->my;
struct permonst *ptr = mtmp->data;
struct monst *mtoo;
boolean costly;
/* cut down the search radius if it thinks character is closer. */
if (distmin(mtmp->mux, mtmp->muy, omx, omy) < SQSRCHRADIUS
@@ -1122,76 +1125,93 @@ m_search_items(struct monst *mtmp, coordxy *ggx, coordxy *ggy, schar *mmoved, in
if (!mtmp->mpeaceful && is_mercenary(ptr))
minr = 1;
if ((!*in_rooms(omx, omy, SHOPBASE) || (!rn2(25) && !mtmp->isshk))) {
oomx = min(COLNO - 1, omx + minr);
oomy = min(ROWNO - 1, omy + minr);
lmx = max(1, omx - minr);
lmy = max(0, omy - minr);
for (otmp = fobj; otmp; otmp = otmp->nobj) {
/* monsters may pick rocks up, but won't go out of their way
to grab them; this might hamper sling wielders, but it cuts
down on move overhead by filtering out most common item */
if (otmp->otyp == ROCK)
continue;
/* avoid special items; once hero picks them up, they'll
cease being special */
if (is_mines_prize(otmp) || is_soko_prize(otmp))
continue;
/* in shop, usually skip */
if (*in_rooms(omx, omy, SHOPBASE) && (rn2(25) || mtmp->isshk))
goto finish_search;
xx = otmp->ox;
yy = otmp->oy;
/* Nymphs take everything. Most other creatures should not
* pick up corpses except as a special case like in
* searches_for_item(). We need to do this check in
* mpickstuff() as well.
*/
if (xx >= lmx && xx <= oomx && yy >= lmy && yy <= oomy) {
/* don't get stuck circling around object that's
underneath an immobile or hidden monster;
paralysis victims excluded */
if ((mtoo = m_at(xx, yy)) != 0
&& (helpless(mtoo) || mtoo->mundetected
|| (mtoo->mappearance && !mtoo->iswiz)
|| !mtoo->data->mmove))
continue;
/* the mfndpos() test for whether to allow a move to a
water location accepts flyers, but they can't reach
underwater objects, so being able to move to a spot
is insufficient for deciding whether to do so */
if (!could_reach_item(mtmp, xx, yy))
continue;
/* distmin() gives a rectangular area */
hmx = min(COLNO - 1, omx + minr);
hmy = min(ROWNO - 1, omy + minr);
lmx = max(1, omx - minr);
lmy = max(0, omy - minr);
/* ignore obj if there's a trap and monster knows it */
if ((ttmp = t_at(xx, yy)) != 0
&& mon_knows_traps(mtmp, ttmp->ttyp)) {
if (*ggx == xx && *ggy == yy) {
*ggx = mtmp->mux;
*ggy = mtmp->muy;
}
continue;
for (xx = lmx; xx <= hmx; xx++) {
for (yy = lmy; yy <= hmy; yy++) {
/* no object here */
if (!OBJ_AT(xx, yy))
continue;
/* found an object closer already */
if (minr < distmin(omx, omy, xx, yy))
continue;
/* the mfndpos() test for whether to allow a move to a
water location accepts flyers, but they can't reach
underwater objects, so being able to move to a spot
is insufficient for deciding whether to do so */
if (!could_reach_item(mtmp, xx, yy))
continue;
/* hiders avoid hero's line of sight */
if (hides_under(ptr) && cansee(xx, yy))
continue;
/* don't get stuck circling around object that's
underneath an immobile or hidden monster;
paralysis victims excluded */
if ((mtoo = m_at(xx, yy)) != 0
&& (helpless(mtoo) || mtoo->mundetected
|| (mtoo->mappearance && !mtoo->iswiz)
|| !mtoo->data->mmove))
continue;
/* Don't get stuck circling an Elbereth */
if (onscary(xx, yy, mtmp))
continue;
/* ignore obj if there's a trap and monster knows it */
if ((ttmp = t_at(xx, yy)) != 0
&& mon_knows_traps(mtmp, ttmp->ttyp)) {
if (*ggx == xx && *ggy == yy) {
*ggx = mtmp->mux;
*ggy = mtmp->muy;
}
continue;
}
/* avoid getting stuck on eg. items in niches */
if (!m_cansee(mtmp, xx, yy))
continue;
if (((mon_would_take_item(mtmp, otmp) && (can_carry(mtmp, otmp) > 0))
|| (hides_under(ptr) && !cansee(otmp->ox, otmp->oy)))
&& can_touch_safely(mtmp, otmp)
/* Don't get stuck circling an Elbereth */
&& !onscary(xx, yy, mtmp)) {
costly = costly_spot(xx, yy);
/* look through the items on this location */
for (otmp = gl.level.objects[xx][yy];
otmp; otmp = otmp->nexthere) {
/* monsters may pick rocks up, but won't go out of their way
to grab them; this might hamper sling wielders, but it cuts
down on move overhead by filtering out most common item */
if (otmp->otyp == ROCK)
continue;
/* avoid special items; once hero picks them up, they'll
cease being special */
if (is_mines_prize(otmp) || is_soko_prize(otmp))
continue;
/* skip shop merchandise */
if (costly && !otmp->no_charge)
continue;
if (mon_would_take_item(mtmp, otmp)
&& (can_carry(mtmp, otmp) > 0)
&& can_touch_safely(mtmp, otmp)) {
minr = distmin(omx, omy, xx, yy);
oomx = min(COLNO - 1, omx + minr);
oomy = min(ROWNO - 1, omy + minr);
lmx = max(1, omx - minr);
lmy = max(0, omy - minr);
*ggx = otmp->ox;
*ggy = otmp->oy;
if (*ggx == omx && *ggy == omy) {
*mmoved = MMOVE_DONE; /* actually unnecessary */
return TRUE;
}
/* found an item of interest; skip the rest of the pile */
break;
}
}
}
}
finish_search:
if (minr < SQSRCHRADIUS && *appr == -1) {
if (distmin(omx, omy, mtmp->mux, mtmp->muy) <= 3) {
*ggx = mtmp->mux;