예제 #1
0
/* the sounds of distressed pets */
void
whimper(struct monst *mtmp)
{
    const char *whimper_verb = 0;

    if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound ||
        !canhear())
        return;

    /* presumably nearness checks have already been made */
    if (Hallucination)
        whimper_verb = h_sounds[rn2(SIZE(h_sounds))];
    else
        switch (mtmp->data->msound) {
        case MS_MEW:
        case MS_GROWL:
            whimper_verb = "whimper";
            break;
        case MS_BARK:
            whimper_verb = "whine";
            break;
        case MS_SQEEK:
            whimper_verb = "squeal";
            break;
        }
    if (whimper_verb) {
        pline("%s %s.", Monnam(mtmp), vtense(NULL, whimper_verb));
        action_interrupted();
        wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 6);
    }
}
예제 #2
0
파일: mthrowu.c 프로젝트: FredrIQ/nhfourk
int
breamq(struct monst *mtmp, int xdef, int ydef, const struct attack *mattk)
{
    /* if new breath types are added, change AD_ACID to max type */
    int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp;

    boolean youdef = u.ux == xdef && u.uy == ydef;

    if (!youdef && distmin(mtmp->mx, mtmp->my, xdef, ydef) < 3)
        return 0;

    boolean linedup = qlined_up(mtmp, xdef, ydef, TRUE, FALSE);

    if (linedup) {
        if (mtmp->mcan) {
            if (canhear()) {
                if (mon_visible(mtmp))
                    pline("%s coughs.", Monnam(mtmp));
                else
                    You_hear("a cough.");
            }
            return 0;
        }
        if (!mtmp->mspec_used && rn2(3)) {
            if ((typ >= AD_MAGM) && (typ <= AD_ACID)) {
                if (mon_visible(mtmp)) {
                    pline("%s breathes %s!", Monnam(mtmp), breathwep[typ - 1]);
                    action_interrupted();
                }
                buzz((int)(-20 - (typ - 1)), (int)mattk->damn, mtmp->mx,
                     mtmp->my, sgn(tbx), sgn(tby), 0);
                /* breath runs out sometimes. Also, give monster some cunning;
                   don't breath if the target fell asleep. */
                if (!rn2(3))
                    mtmp->mspec_used = 10 + rn2(20);
                boolean sleeping = youdef ? u_helpless(hm_asleep) :
                    MON_AT(level, xdef, ydef) ?
                    m_at(level, xdef, ydef)->msleeping : FALSE;
                if (typ == AD_SLEE && sleeping)
                    mtmp->mspec_used += rnd(20);
            } else
                impossible("Breath weapon %d used", typ - 1);
        }
    }
    return 1;
}
예제 #3
0
/* the sounds of a seriously abused pet, including player attacking it */
void
growl(struct monst *mtmp)
{
    const char *growl_verb = 0;

    if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound ||
        !canhear())
        return;

    /* presumably nearness checks have already been made */
    if (Hallucination)
        growl_verb = h_sounds[rn2(SIZE(h_sounds))];
    else
        growl_verb = growl_sound(mtmp);
    if (growl_verb) {
        pline("%s %s!", Monnam(mtmp), vtense(NULL, growl_verb));
        action_interrupted();
        wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 18);
    }
}
예제 #4
0
파일: mthrowu.c 프로젝트: FredrIQ/nhfourk
int
spitmq(struct monst *mtmp, int xdef, int ydef, const struct attack *mattk)
{
    struct obj *otmp;

    if (mtmp->mcan) {
        if (canhear())
            pline("A dry rattle comes from %s throat.",
                  s_suffix(mon_nam(mtmp)));
        return 0;
    }
    boolean linedup = qlined_up(mtmp, xdef, ydef, FALSE, FALSE);
    if (linedup && ai_use_at_range(
            BOLT_LIM - distmin(mtmp->mx, mtmp->my, xdef, ydef))) {
        switch (mattk->adtyp) {
        case AD_BLND:
        case AD_DRST:
            otmp = mktemp_sobj(level, BLINDING_VENOM);
            break;
        case AD_DRLI:
            otmp = mktemp_sobj(level, VAMPIRE_BLOOD);
            break;
        default:
            impossible("bad attack type in spitm");
            /* fall through */
        case AD_ACID:
            otmp = mktemp_sobj(level, ACID_VENOM);
            break;
        }

        if (mon_visible(mtmp)) {
            pline("%s spits venom!", Monnam(mtmp));
            action_interrupted();
        }
        m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby),
                distmin(mtmp->mx, mtmp->my, xdef, ydef), otmp,
                FALSE);
        return 1;
    }
    return 0;
}
예제 #5
0
/* the sounds of mistreated pets */
void
yelp(struct monst *mtmp)
{
    const char *yelp_verb = 0;

    if (mtmp->msleeping || !mtmp->mcanmove || !mtmp->data->msound ||
        !canhear())
        return;

    /* presumably nearness checks have already been made */
    if (Hallucination)
        yelp_verb = h_sounds[rn2(SIZE(h_sounds))];
    else
        switch (mtmp->data->msound) {
        case MS_MEW:
            yelp_verb = "yowl";
            break;
        case MS_BARK:
        case MS_GROWL:
            yelp_verb = "yelp";
            break;
        case MS_ROAR:
            yelp_verb = "snarl";
            break;
        case MS_SQEEK:
            yelp_verb = "squeal";
            break;
        case MS_SQAWK:
            yelp_verb = "screak";
            break;
        case MS_WAIL:
            yelp_verb = "wail";
            break;
        }
    if (yelp_verb) {
        pline("%s %s!", Monnam(mtmp), vtense(NULL, yelp_verb));
        action_interrupted();
        wake_nearto(mtmp->mx, mtmp->my, mtmp->data->mlevel * 12);
    }
}
예제 #6
0
void
teleds(int nux, int nuy, boolean allow_drag)
{
    boolean ball_active = (Punished &&
                           uball->where != OBJ_FREE), ball_still_in_range =
        FALSE;

    /* If they have to move the ball, then drag if allow_drag is true;
       otherwise they are teleporting, so unplacebc(). If they don't have to
       move the ball, then always "drag" whether or not allow_drag is true,
       because we are calling that function, not to drag, but to move the
       chain.  *However* there are some dumb special cases: 0 0 _X move east
       -----> X_ @ @ These are permissible if teleporting, but not if dragging.
       As a result, drag_ball() needs to know about allow_drag and might end up
       dragging the ball anyway.  Also, drag_ball() might find that dragging
       the ball is completely impossible (ball in range but there's rock in the
       way), in which case it teleports the ball on its own. */
    if (ball_active) {
        if (!carried(uball) && distmin(nux, nuy, uball->ox, uball->oy) <= 2)
            ball_still_in_range = TRUE; /* don't have to move the ball */
        else {
            /* have to move the ball */
            if (!allow_drag || distmin(u.ux, u.uy, nux, nuy) > 1) {
                /* we should not have dist > 1 and allow_drag at the same time,
                   but just in case, we must then revert to teleport. */
                allow_drag = FALSE;
                unplacebc();
            }
        }
    }
    u.utrap = 0;
    u.ustuck = 0;
    u.ux0 = u.ux;
    u.uy0 = u.uy;

    if (hides_under(youmonst.data))
        u.uundetected = OBJ_AT(nux, nuy);
    else if (youmonst.data->mlet == S_EEL)
        u.uundetected = is_pool(level, nux, nuy);
    else {
        u.uundetected = 0;
        /* mimics stop being unnoticed */
        if (youmonst.data->mlet == S_MIMIC)
            youmonst.m_ap_type = M_AP_NOTHING;
    }

    if (Engulfed) {
        u.uswldtim = Engulfed = 0;
        if (Punished && !ball_active) {
            /* ensure ball placement, like unstuck */
            ball_active = TRUE;
            allow_drag = FALSE;
        }
        doredraw();
    }
    if (ball_active) {
        if (ball_still_in_range || allow_drag) {
            int bc_control;
            xchar ballx, bally, chainx, chainy;
            boolean cause_delay;

            if (drag_ball
                (nux, nuy, &bc_control, &ballx, &bally, &chainx, &chainy,
                 &cause_delay, allow_drag))
                move_bc(0, bc_control, ballx, bally, chainx, chainy);
        }
    }
    /* must set u.ux, u.uy after drag_ball(), which may need to know the old
       position if allow_drag is true... */
    u.ux = nux;
    u.uy = nuy;
    fill_pit(level, u.ux0, u.uy0);
    if (ball_active) {
        if (!ball_still_in_range && !allow_drag)
            placebc();
    }
    initrack(); /* teleports mess up tracking monsters without this */
    update_player_regions(level);
    /* Move your steed, too */
    if (u.usteed) {
        u.usteed->mx = nux;
        u.usteed->my = nuy;
    }

    /*
     *  Make sure the hero disappears from the old location.  This will
     *  not happen if she is teleported within sight of her previous
     *  location.  Force a full vision recalculation because the hero
     *  is now in a new location.
     */
    newsym(u.ux0, u.uy0);
    see_monsters(FALSE);
    turnstate.vision_full_recalc = TRUE;
    action_interrupted();
    vision_recalc(0);   /* vision before effects */
    spoteffects(TRUE);
    invocation_message();
}
예제 #7
0
/* return TRUE if the caller needs to place the ball and chain down again
 *
 *  Should not be called while swallowed.  Should be called before movement,
 *  because we might want to move the ball or chain to the hero's old position.
 *
 * It is called if we are moving.  It is also called if we are teleporting
 * *if* the ball doesn't move and we thus must drag the chain.  It is not
 * called for ordinary teleportation.
 *
 * allow_drag is only used in the ugly special case where teleporting must
 * drag the chain, while an identical-looking movement must drag both the ball
 * and chain.
 */
boolean
drag_ball(xchar x, xchar y, int *bc_control, xchar * ballx, xchar * bally,
          xchar * chainx, xchar * chainy, boolean * cause_delay,
          boolean allow_drag)
{
    struct trap *t = NULL;
    boolean already_in_rock;

    *ballx = uball->ox;
    *bally = uball->oy;
    *chainx = uchain->ox;
    *chainy = uchain->oy;
    *bc_control = 0;
    *cause_delay = FALSE;

    if (dist2(x, y, uchain->ox, uchain->oy) <= 2) {     /* nothing moved */
        move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
        return TRUE;
    }

    /* only need to move the chain? */
    if (carried(uball) || distmin(x, y, uball->ox, uball->oy) <= 2) {
        xchar oldchainx = uchain->ox, oldchainy = uchain->oy;

        *bc_control = BC_CHAIN;
        move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
        if (carried(uball)) {
            /* move chain only if necessary */
            if (distmin(x, y, uchain->ox, uchain->oy) > 1) {
                *chainx = u.ux;
                *chainy = u.uy;
            }
            return TRUE;
        }
#define CHAIN_IN_MIDDLE(chx, chy) \
    (distmin(x, y, chx, chy) <= 1 && \
     distmin(chx, chy, uball->ox, uball->oy) <= 1)
#define IS_CHAIN_ROCK(x,y) \
    (IS_ROCK(level->locations[x][y].typ) || \
     (IS_DOOR(level->locations[x][y].typ) && \
      (level->locations[x][y].doormask & (D_CLOSED|D_LOCKED))))
/* Don't ever move the chain into solid rock.  If we have to, then instead
 * undo the move_bc() and jump to the drag ball code.  Note that this also
 * means the "cannot carry and drag" message will not appear, since unless we
 * moved at least two squares there is no possibility of the chain position
 * being in solid rock.
 */
#define SKIP_TO_DRAG { *chainx = oldchainx; *chainy = oldchainy; \
    move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy); \
    goto drag; }
        if (IS_CHAIN_ROCK(u.ux, u.uy) || IS_CHAIN_ROCK(*chainx, *chainy)
            || IS_CHAIN_ROCK(uball->ox, uball->oy))
            already_in_rock = TRUE;
        else
            already_in_rock = FALSE;

        switch (dist2(x, y, uball->ox, uball->oy)) {
            /* two spaces diagonal from ball, move chain inbetween */
        case 8:
            *chainx = (uball->ox + x) / 2;
            *chainy = (uball->oy + y) / 2;
            if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
                SKIP_TO_DRAG;
            break;

            /* player is distance 2/1 from ball; move chain to one of the two
               spaces between @ __ 0 */
        case 5:{
                xchar tempx, tempy, tempx2, tempy2;

                /* find position closest to current position of chain */
                /* no effect if current position is already OK */
                if (abs(x - uball->ox) == 1) {
                    tempx = x;
                    tempx2 = uball->ox;
                    tempy = tempy2 = (uball->oy + y) / 2;
                } else {
                    tempx = tempx2 = (uball->ox + x) / 2;
                    tempy = y;
                    tempy2 = uball->oy;
                }
                if (IS_CHAIN_ROCK(tempx, tempy) &&
                    !IS_CHAIN_ROCK(tempx2, tempy2) && !already_in_rock) {
                    if (allow_drag) {
                        /* Avoid pathological case *if* not teleporting: 0 0_
                           _X move northeast -----> X@ @ */
                        if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 &&
                            dist2(x, y, tempx, tempy) == 1)
                            SKIP_TO_DRAG;
                        /* Avoid pathological case *if* not teleporting: 0 0 _X 
                           move east -----> X_ @ @ */
                        if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 &&
                            dist2(x, y, tempx, tempy) == 2)
                            SKIP_TO_DRAG;
                    }
                    *chainx = tempx2;
                    *chainy = tempy2;
                } else if (!IS_CHAIN_ROCK(tempx, tempy) &&
                           IS_CHAIN_ROCK(tempx2, tempy2) && !already_in_rock) {
                    if (allow_drag) {
                        if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 5 &&
                            dist2(x, y, tempx2, tempy2) == 1)
                            SKIP_TO_DRAG;
                        if (dist2(u.ux, u.uy, uball->ox, uball->oy) == 4 &&
                            dist2(x, y, tempx2, tempy2) == 2)
                            SKIP_TO_DRAG;
                    }
                    *chainx = tempx;
                    *chainy = tempy;
                } else if (IS_CHAIN_ROCK(tempx, tempy) &&
                           IS_CHAIN_ROCK(tempx2, tempy2) && !already_in_rock) {
                    SKIP_TO_DRAG;
                } else if (dist2(tempx, tempy, uchain->ox, uchain->oy) <
                           dist2(tempx2, tempy2, uchain->ox, uchain->oy) ||
                           ((dist2(tempx, tempy, uchain->ox, uchain->oy) ==
                             dist2(tempx2, tempy2, uchain->ox, uchain->oy)) &&
                            rn2(2))) {
                    *chainx = tempx;
                    *chainy = tempy;
                } else {
                    *chainx = tempx2;
                    *chainy = tempy2;
                }
                break;
            }

            /* ball is two spaces horizontal or vertical from player; move */
            /* chain inbetween *unless* current chain position is OK */
        case 4:
            if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
                break;
            *chainx = (x + uball->ox) / 2;
            *chainy = (y + uball->oy) / 2;
            if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
                SKIP_TO_DRAG;
            break;

            /* ball is one space diagonal from player.  Check for the following 
               special case: @ _ moving southwest becomes @_ 0 0 (This will
               also catch teleporting that happens to resemble this case, but
               oh well.) Otherwise fall through. */
        case 2:
            if (dist2(x, y, uball->ox, uball->oy) == 2 &&
                dist2(x, y, uchain->ox, uchain->oy) == 4) {
                if (uchain->oy == y)
                    *chainx = uball->ox;
                else
                    *chainy = uball->oy;
                if (IS_CHAIN_ROCK(*chainx, *chainy) && !already_in_rock)
                    SKIP_TO_DRAG;
                break;
            }
            /* fall through */
        case 1:
        case 0:
            /* do nothing if possible */
            if (CHAIN_IN_MIDDLE(uchain->ox, uchain->oy))
                break;
            /* otherwise try to drag chain to player's old position */
            if (CHAIN_IN_MIDDLE(u.ux, u.uy)) {
                *chainx = u.ux;
                *chainy = u.uy;
                break;
            }
            /* otherwise use player's new position (they must have teleported,
               for this to happen) */
            *chainx = x;
            *chainy = y;
            break;

        default:
            impossible("bad chain movement");
            break;
        }
#undef SKIP_TO_DRAG
#undef IS_CHAIN_ROCK
#undef CHAIN_IN_MIDDLE
        return TRUE;
    }

drag:

    if (near_capacity() > SLT_ENCUMBER && dist2(x, y, u.ux, u.uy) <= 2) {
        pline("You cannot %sdrag the heavy iron ball.",
              invent ? "carry all that and also " : "");
        action_completed();
        return FALSE;
    }

    if ((is_pool(level, uchain->ox, uchain->oy) &&
         /* water not mere continuation of previous water */
         (level->locations[uchain->ox][uchain->oy].typ == POOL ||
          !is_pool(level, uball->ox, uball->oy) ||
          level->locations[uball->ox][uball->oy].typ == POOL))
        || ((t = t_at(level, uchain->ox, uchain->oy)) &&
            (t->ttyp == PIT || t->ttyp == SPIKED_PIT || t->ttyp == HOLE ||
             t->ttyp == TRAPDOOR))) {

        if (Levitation) {
            pline("You feel a tug from the iron ball.");
            if (t)
                t->tseen = 1;
        } else {
            struct monst *victim;

            pline("You are jerked back by the iron ball!");
            if ((victim = m_at(level, uchain->ox, uchain->oy)) != 0) {
                int tmp;

                tmp = -2 + Luck + find_mac(victim);
                tmp += omon_adj(victim, uball, TRUE);
                if (tmp >= rnd(20))
                    hmon(victim, uball, 1);
                else
                    miss(xname(uball), victim);

            }   /* now check again in case mon died */
            if (!m_at(level, uchain->ox, uchain->oy)) {
                u.ux = uchain->ox;
                u.uy = uchain->oy;
                newsym(u.ux0, u.uy0);
            }
            action_interrupted();

            *bc_control = BC_BALL;
            move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
            *ballx = uchain->ox;
            *bally = uchain->oy;
            move_bc(0, *bc_control, *ballx, *bally, *chainx, *chainy);
            spoteffects(TRUE);
            return FALSE;
        }
    }

    *bc_control = BC_BALL | BC_CHAIN;

    move_bc(1, *bc_control, *ballx, *bally, *chainx, *chainy);
    if (dist2(x, y, u.ux, u.uy) > 2) {
        /* Awful case: we're still in range of the ball, so we thought we could 
           only move the chain, but it turned out that the target square for
           the chain was rock, so we had to drag it instead. But we can't drag
           it either, because we teleported and are more than one square from
           our old position.  Revert to the teleport behavior. */
        *ballx = *chainx = x;
        *bally = *chainy = y;
    } else {
        *ballx = uchain->ox;
        *bally = uchain->oy;
        *chainx = u.ux;
        *chainy = u.uy;
    }
    *cause_delay = TRUE;
    return TRUE;
}
예제 #8
0
파일: engrave.c 프로젝트: FredrIQ/nethack4
void
read_engr_at(int x, int y)
{
    struct engr *ep = engr_at(level, x, y);
    int sensed = 0;

    /* Sensing an engraving does not require sight, nor does it necessarily
       imply comprehension (literacy). */
    if (ep && ep->engr_txt[0] &&
        /* Don't stop if travelling or autoexploring. */
        !(travelling() && level->locations[x][y].mem_stepped)) {
        switch (ep->engr_type) {
        case DUST:
            if (!Blind) {
                sensed = 1;
                pline("Something is written here in the %s.",
                      is_ice(level, x, y) ? "frost" : "dust");
            }
            break;
        case ENGRAVE:
        case HEADSTONE:
            if (!Blind || can_reach_floor()) {
                sensed = 1;
                pline("Something is engraved here on the %s.", surface(x, y));
            }
            break;
        case BURN:
            if (!Blind || can_reach_floor()) {
                sensed = 1;
                pline("Some text has been %s into the %s here.",
                      is_ice(level, x, y) ? "melted" : "burned", surface(x, y));
            }
            break;
        case MARK:
            if (!Blind) {
                sensed = 1;
                pline("There's some graffiti on the %s here.", surface(x, y));
            }
            break;
        case ENGR_BLOOD:
            /* "It's a message! Scrawled in blood!" "What's it say?" "It
               says... `See you next Wednesday.'" -- Thriller */
            if (!Blind) {
                sensed = 1;
                pline("You see a message scrawled in blood here.");
            }
            break;
        default:
            impossible("Something is written in a very strange way.");
            sensed = 1;
        }
        if (sensed) {
            /* AIS: Bounds check removed, because pline can now handle
               arbitrary-length strings */
            char *et = ep->engr_txt;
            pline("You %s: \"%s\".", (Blind) ? "feel the words" : "read", et);
            /* TODO: For now, engravings stop farmove, autoexplore, and
               travel. This can get quite spammy, though. */
            if (flags.occupation == occ_move ||
                flags.occupation == occ_travel ||
                flags.occupation == occ_autoexplore)
                action_interrupted();
        }
    }
}
예제 #9
0
파일: mthrowu.c 프로젝트: FredrIQ/nhfourk
/* monster attempts ranged weapon attack against a square */
void
thrwmq(struct monst *mtmp, int xdef, int ydef)
{
    struct obj *otmp, *mwep;
    schar skill;
    int multishot;
    const char *onm;

    /* Rearranged beginning so monsters can use polearms not in a line */
    if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) {
        mtmp->weapon_check = NEED_RANGED_WEAPON;
        /* mon_wield_item resets weapon_check as appropriate */
        if (mon_wield_item(mtmp) != 0)
            return;
    }

    /* Pick a weapon */
    otmp = select_rwep(mtmp);
    if (!otmp)
        return;

    if (is_pole(otmp)) {
        int dam, hitv;

        if (otmp != MON_WEP(mtmp))
            return;  /* polearm must be wielded */

        /* TODO: LOE function between two arbitrary points. */
        if (dist2(mtmp->mx, mtmp->my, xdef, ydef) > POLE_LIM ||
            (xdef == u.ux && ydef == u.uy && !couldsee(mtmp->mx, mtmp->my)))
            return;     /* Out of range, or intervening wall */

        if (mon_visible(mtmp)) {
            onm = singular(otmp, xname);
            pline("%s thrusts %s.", Monnam(mtmp),
                  obj_is_pname(otmp) ? the(onm) : an(onm));
        }

        if (xdef == u.ux && ydef == u.uy) {

            dam = dmgval(otmp, &youmonst);
            hitv = 3 - distmin(u.ux, u.uy, mtmp->mx, mtmp->my);
            if (hitv < -4)
                hitv = -4;
            if (bigmonst(youmonst.data))
                hitv++;
            hitv += 8 + otmp->spe;
            if (objects[otmp->otyp].oc_class == WEAPON_CLASS ||
                objects[otmp->otyp].oc_class == VENOM_CLASS)
                hitv += objects[otmp->otyp].oc_hitbon;
            if (dam < 1)
                dam = 1;

            thitu(hitv, dam, otmp, NULL);
            action_interrupted();

        } else if (MON_AT(level, xdef, ydef))
            (void)ohitmon(m_at(level, xdef, ydef), otmp, 0, FALSE);
        else if (mon_visible(mtmp))
            pline("But it misses wildly.");

        return;
    }

    if (!qlined_up(mtmp, xdef, ydef, FALSE, FALSE) ||
        !ai_use_at_range(BOLT_LIM - distmin(mtmp->mx, mtmp->my, xdef, ydef)))
        return;

    skill = objects[otmp->otyp].oc_skill;
    mwep = MON_WEP(mtmp);       /* wielded weapon */

    /* Multishot calculations */
    multishot = 1;
    if ((ammo_and_launcher(otmp, mwep) || skill == P_DAGGER || skill == -P_DART
         || skill == -P_SHURIKEN) && !mtmp->mconf) {
        /* Assumes lords are skilled, princes are expert */
        if (is_prince(mtmp->data))
            multishot += 2;
        else if (is_lord(mtmp->data))
            multishot++;

        switch (monsndx(mtmp->data)) {
        case PM_RANGER:
            multishot++;
            break;
        case PM_ROGUE:
            if (skill == P_DAGGER)
                multishot++;
            break;
        case PM_NINJA:
        case PM_SAMURAI:
            if (otmp->otyp == YA && mwep && mwep->otyp == YUMI)
                multishot++;
            break;
        default:
            break;
        }
        /* racial bonus */
        if ((is_elf(mtmp->data) && otmp->otyp == ELVEN_ARROW && mwep &&
             mwep->otyp == ELVEN_BOW) || (is_orc(mtmp->data) &&
                                          otmp->otyp == ORCISH_ARROW && mwep &&
                                          mwep->otyp == ORCISH_BOW))
            multishot++;

        if ((long)multishot > otmp->quan)
            multishot = (int)otmp->quan;
        if (multishot < 1)
            multishot = 1;
        else
            multishot = rnd(multishot);
    }

    if (mon_visible(mtmp)) {
        if (multishot > 1) {
            /* "N arrows"; multishot > 1 implies otmp->quan > 1, so xname()'s
               result will already be pluralized */
            onm = msgprintf("%d %s", multishot, xname(otmp));
        } else {
            /* "an arrow" */
            onm = singular(otmp, xname);
            onm = obj_is_pname(otmp) ? the(onm) : an(onm);
        }
        m_shot.s = ammo_and_launcher(otmp, mwep) ? TRUE : FALSE;
        pline("%s %s %s!", Monnam(mtmp), m_shot.s ? "shoots" : "throws", onm);
        m_shot.o = otmp->otyp;
    } else {
        m_shot.o = STRANGE_OBJECT;      /* don't give multishot feedback */
    }

    m_shot.n = multishot;
    for (m_shot.i = 1; m_shot.i <= m_shot.n; m_shot.i++) {
        m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby),
                distmin(mtmp->mx, mtmp->my, xdef, ydef), otmp, TRUE);
        /* conceptually all N missiles are in flight at once, but
           if mtmp gets killed (shot kills adjacent gas spore and
           triggers explosion, perhaps), inventory will be dropped
           and otmp might go away via merging into another stack;
           if we then use it, we could cause undefined behavior */
        if (mtmp->mhp <= 0 && m_shot.i < m_shot.n) {
            /* cancel pending shots (ought to give a message here since
               we gave one above about throwing/shooting N missiles) */
            break;  /* endmultishot(FALSE); */
        }
    }
    m_shot.n = m_shot.i = 0;
    m_shot.o = STRANGE_OBJECT;
    m_shot.s = FALSE;

    action_interrupted();
}
예제 #10
0
파일: mthrowu.c 프로젝트: FredrIQ/nhfourk
void
m_throw(struct monst *mon, int x, int y, int dx, int dy, int range,
        struct obj *obj, boolean verbose)
{
    struct monst *mtmp;
    struct obj *singleobj;
    struct tmp_sym *tsym = 0;
    int hitu, blindinc = 0;

    bhitpos.x = x;
    bhitpos.y = y;

    if (obj->quan == 1L) {
        /*
         * Remove object from minvent.  This cannot be done later on;
         * what if the player dies before then, leaving the monster
         * with 0 daggers?  (This caused the infamous 2^32-1 orcish
         * dagger bug).
         *
         * VENOM is not in minvent - it should already be OBJ_FREE.
         * The extract below does nothing.
         */

        /* not possibly_unwield, which checks the object's */
        /* location, not its existence */
        if (MON_WEP(mon) == obj) {
            setmnotwielded(mon, obj);
            MON_NOWEP(mon);
        }
        obj_extract_self(obj);
        singleobj = obj;
        obj = NULL;
    } else {
        singleobj = splitobj(obj, 1L);
        obj_extract_self(singleobj);
    }

    singleobj->owornmask = 0;   /* threw one of multiple weapons in hand? */
    singleobj->olev = level;    /* object is on the same level as monster */

    if ((singleobj->cursed || singleobj->greased) && (dx || dy) && !rn2(7)) {
        if (canseemon(mon) && flags.verbose) {
            if (is_ammo(singleobj))
                pline("%s misfires!", Monnam(mon));
            else
                pline("%s as %s throws it!", Tobjnam(singleobj, "slip"),
                      mon_nam(mon));
        }
        dx = rn2(3) - 1;
        dy = rn2(3) - 1;
        /* check validity of new direction */
        if (!dx && !dy) {
            drop_throw(singleobj, 0, bhitpos.x, bhitpos.y);
            return;
        }
    }

    /* pre-check for doors, walls and boundaries. Also need to pre-check for
       bars regardless of direction; the random chance for small objects
       hitting bars is skipped when reaching them at point blank range */
    if (!isok(bhitpos.x + dx, bhitpos.y + dy)
        || IS_ROCK(level->locations[bhitpos.x + dx][bhitpos.y + dy].typ)
        || closed_door(level, bhitpos.x + dx, bhitpos.y + dy)
        || (level->locations[bhitpos.x + dx][bhitpos.y + dy].typ == IRONBARS &&
            hits_bars(&singleobj, bhitpos.x, bhitpos.y, 0, 0))) {
        drop_throw(singleobj, 0, bhitpos.x, bhitpos.y);
        return;
    }

    /* Note: drop_throw may destroy singleobj.  Since obj must be destroyed
       early to avoid the dagger bug, anyone who modifies this code should be
       careful not to use either one after it's been freed. */
    tsym = tmpsym_initobj(singleobj);

    while (range-- > 0) {      /* Actually the loop is always exited by break */
        bhitpos.x += dx;
        bhitpos.y += dy;
        if ((mtmp = m_at(level, bhitpos.x, bhitpos.y)) != 0) {
            if (ohitmon(mtmp, singleobj, range, verbose))
                break;
        } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) {
            action_interrupted();

            if (singleobj->oclass == GEM_CLASS &&
                singleobj->otyp <= LAST_GEM + 9
                /* 9 glass colors */
                && is_unicorn(youmonst.data) && !u_helpless(hm_all)) {
                if (singleobj->otyp > LAST_GEM) {
                    pline("You catch the %s.", xname(singleobj));
                    pline("You are not interested in %s junk.",
                          s_suffix(mon_nam(mon)));
                    makeknown(singleobj->otyp);
                    dropy(singleobj);
                } else {
                    pline("You accept %s gift in the spirit in which it was "
                          "intended.", s_suffix(mon_nam(mon)));
                    hold_another_object(singleobj, "You catch, but drop, %s.",
                                        xname(singleobj), "You catch:");
                }
                break;
            }
            if (singleobj->oclass == POTION_CLASS) {
                if (!Blind)
                    singleobj->dknown = 1;
                potionhit(&youmonst, singleobj, FALSE);
                break;
            }
            switch (singleobj->otyp) {
                int dam, hitv;

            case EGG:
                if (!touch_petrifies(&mons[singleobj->corpsenm])) {
                    impossible("monster throwing egg type %d",
                               singleobj->corpsenm);
                    hitu = 0;
                    break;
                }
                /* fall through */
            case CREAM_PIE:
            case BLINDING_VENOM:
                hitu = thitu(8, 0, singleobj, NULL);
                break;
            default:
                dam = dmgval(singleobj, &youmonst);
                hitv = 3 - distmin(u.ux, u.uy, mon->mx, mon->my);
                if (hitv < -4)
                    hitv = -4;
                if (is_elf(mon->data) &&
                    objects[singleobj->otyp].oc_skill == P_BOW) {
                    hitv++;
                    if (MON_WEP(mon) && MON_WEP(mon)->otyp == ELVEN_BOW)
                        hitv++;
                    if (singleobj->otyp == ELVEN_ARROW)
                        dam++;
                }
                if (bigmonst(youmonst.data))
                    hitv++;
                hitv += 8 + singleobj->spe;
                if (dam < 1)
                    dam = 1;
                if (objects[singleobj->otyp].oc_class == WEAPON_CLASS ||
                    objects[singleobj->otyp].oc_class == VENOM_CLASS) {
                    hitv += objects[singleobj->otyp].oc_hitbon;
                }
                hitu = thitu(hitv, dam, singleobj, NULL);
            }
            if (hitu && singleobj->opoisoned && is_poisonable(singleobj)) {
                poisoned(xname(singleobj), A_STR,
                         killer_msg_obj(POISONING, singleobj), -10);
            }
            if (hitu &&
                can_blnd(NULL, &youmonst,
                         (uchar) (singleobj->otyp ==
                                  BLINDING_VENOM ? AT_SPIT : AT_WEAP),
                         singleobj)) {
                blindinc = rnd(25);
                if (singleobj->otyp == CREAM_PIE) {
                    if (!Blind)
                        pline("Yecch!  You've been creamed.");
                    else
                        pline("There's something sticky all over your %s.",
                              body_part(FACE));
                } else if (singleobj->otyp == BLINDING_VENOM) {
                    int num_eyes = eyecount(youmonst.data);

                    /* venom in the eyes */
                    if (!Blind)
                        pline("The venom blinds you.");
                    else
                        pline("Your %s sting%s.",
                              (num_eyes ==
                               1) ? body_part(EYE) : makeplural(body_part(EYE)),
                              (num_eyes == 1) ? "s" : "");
                }
            }
            if (hitu && singleobj->otyp == VAMPIRE_BLOOD) {
                if (!Drain_resistance) {
                    losexp("vampire blood", FALSE);
                }
            }
            if (hitu && singleobj->otyp == EGG) {
                if (touched_monster(singleobj->corpsenm))
                    Stoned = 5;
            }
            action_interrupted();
            if (hitu || !range) {
                drop_throw(singleobj, hitu, u.ux, u.uy);
                break;
            }
        } else if (!range       /* reached end of path */
                   /* missile hits edge of screen */
                   || !isok(bhitpos.x + dx, bhitpos.y + dy)
                   /* missile hits the wall */
                   || IS_ROCK(level->
                              locations[bhitpos.x + dx][bhitpos.y + dy].typ)
                   /* missile hit closed door */
                   || closed_door(level, bhitpos.x + dx, bhitpos.y + dy)
                   /* missile might hit iron bars */
                   || (level->locations[bhitpos.x + dx][bhitpos.y + dy].typ ==
                       IRONBARS &&
                       hits_bars(&singleobj, bhitpos.x, bhitpos.y, !rn2(5), 0))
                   /* Thrown objects "sink" */
                   || IS_SINK(level->locations[bhitpos.x][bhitpos.y].typ)) {
            if (singleobj)      /* hits_bars might have destroyed it */
                drop_throw(singleobj, 0, bhitpos.x, bhitpos.y);
            break;
        }
        tmpsym_at(tsym, bhitpos.x, bhitpos.y);
        win_delay_output();
    }
    tmpsym_at(tsym, bhitpos.x, bhitpos.y);
    win_delay_output();
    tmpsym_end(tsym);

    if (blindinc) {
        u.ucreamed += blindinc;
        make_blinded(Blinded + (long)blindinc, FALSE);
        if (!Blind)
            pline("Your vision quickly clears.");
        else if (flags.verbose)
            pline("Use the command #wipe to clean your %s.", body_part(FACE));
    }
}
예제 #11
0
파일: explode.c 프로젝트: FredrIQ/nhfourk
/* returns number of scattered objects */
long
scatter(int sx, int sy, /* location of objects to scatter */
        int blastforce, /* force behind the scattering */
        unsigned int scflags, struct obj *obj)
{   /* only scatter this obj */
    struct obj *otmp;
    struct level *lev = obj ? obj->olev : level;
    int tmp;
    int farthest = 0;
    uchar typ;
    long qtmp;
    boolean used_up;
    boolean individual_object = obj ? TRUE : FALSE;
    struct monst *mtmp;
    struct scatter_chain *stmp, *stmp2 = 0;
    struct scatter_chain *schain = NULL;
    long total = 0L;
    boolean visible = (lev == level && cansee(sx, sy));

    while ((otmp = individual_object ? obj : lev->objects[sx][sy]) != 0) {
        if (otmp->quan > 1L) {
            qtmp = otmp->quan - 1;
            if (qtmp > LARGEST_INT)
                qtmp = LARGEST_INT;
            qtmp = (long)rnd((int)qtmp);
            otmp = splitobj(otmp, qtmp);
        } else {
            obj = NULL; /* all used */
        }
        obj_extract_self(otmp);
        used_up = FALSE;

        /* 9 in 10 chance of fracturing boulders or statues */
        if ((scflags & MAY_FRACTURE)
                && ((otmp->otyp == BOULDER) || (otmp->otyp == STATUE))
                && rn2(10)) {
            if (otmp->otyp == BOULDER) {
                if (visible)
                    pline("%s apart.", Tobjnam(otmp, "break"));
                fracture_rock(otmp);
                place_object(otmp, lev, sx, sy);
                if ((otmp = sobj_at(BOULDER, lev, sx, sy)) != 0) {
                    /* another boulder here, restack it to the top */
                    obj_extract_self(otmp);
                    place_object(otmp, lev, sx, sy);
                }
            } else {
                struct trap *trap;

                if ((trap = t_at(lev, sx, sy)) && trap->ttyp == STATUE_TRAP)
                    deltrap(lev, trap);
                if (visible)
                    pline("%s.", Tobjnam(otmp, "crumble"));
                break_statue(otmp);
                place_object(otmp, lev, sx, sy); /* put fragments on floor */
            }
            used_up = TRUE;

            /* 1 in 10 chance of destruction of obj; glass, egg destruction */
        } else if ((scflags & MAY_DESTROY) &&
                   (!rn2(10) || (objects[otmp->otyp].oc_material == GLASS ||
                                 otmp->otyp == EGG))) {
            if (breaks(otmp, (xchar) sx, (xchar) sy))
                used_up = TRUE;
        }

        if (!used_up) {
            stmp = malloc(sizeof (struct scatter_chain));
            stmp->next = NULL;
            stmp->obj = otmp;
            stmp->ox = sx;
            stmp->oy = sy;
            tmp = rn2(8);       /* get the direction */
            stmp->dx = xdir[tmp];
            stmp->dy = ydir[tmp];
            tmp = blastforce - (otmp->owt / 40);
            if (tmp < 1)
                tmp = 1;
            stmp->range = rnd(tmp);     /* anywhere up to that determ. by wt */
            if (farthest < stmp->range)
                farthest = stmp->range;
            stmp->stopped = FALSE;
            if (!schain)
                schain = stmp;
            else
                stmp2->next = stmp;
            stmp2 = stmp;
        }
    }

    while (farthest-- > 0) {
        for (stmp = schain; stmp; stmp = stmp->next) {
            if ((stmp->range-- > 0) && (!stmp->stopped)) {
                bhitpos.x = stmp->ox + stmp->dx;
                bhitpos.y = stmp->oy + stmp->dy;
                typ = lev->locations[bhitpos.x][bhitpos.y].typ;
                if (!isok(bhitpos.x, bhitpos.y)) {
                    bhitpos.x -= stmp->dx;
                    bhitpos.y -= stmp->dy;
                    stmp->stopped = TRUE;
                } else if (!ZAP_POS(typ) ||
                           closed_door(lev, bhitpos.x, bhitpos.y)) {
                    bhitpos.x -= stmp->dx;
                    bhitpos.y -= stmp->dy;
                    stmp->stopped = TRUE;
                } else if ((mtmp = m_at(lev, bhitpos.x, bhitpos.y)) != 0) {
                    if (scflags & MAY_HITMON) {
                        stmp->range--;
                        if (ohitmon(mtmp, stmp->obj, 1, FALSE)) {
                            stmp->obj = NULL;
                            stmp->stopped = TRUE;
                        }
                    }
                } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) {
                    if (scflags & MAY_HITYOU) {
                        int hitvalu, hitu;

                        action_interrupted();

                        hitvalu = 8 + stmp->obj->spe;
                        if (bigmonst(youmonst.data))
                            hitvalu++;
                        hitu =
                            thitu(hitvalu, dmgval(stmp->obj, &youmonst),
                                  stmp->obj, NULL);
                        if (hitu)
                            stmp->range -= 3;
                    }
                } else {
                    if (scflags & VIS_EFFECTS) {
                        /* tmpsym_at(bhitpos.x, bhitpos.y); */
                        /* delay_output(); */
                    }
                }
                stmp->ox = bhitpos.x;
                stmp->oy = bhitpos.y;
            }
        }
    }
    for (stmp = schain; stmp; stmp = stmp2) {
        int x, y;

        stmp2 = stmp->next;
        x = stmp->ox;
        y = stmp->oy;
        if (stmp->obj) {
            if (x != sx || y != sy)
                total += stmp->obj->quan;
            place_object(stmp->obj, lev, x, y);
            stackobj(stmp->obj);
        }
        free(stmp);
        if (lev == level)
            newsym(x, y);
    }

    return total;
}
예제 #12
0
void
invault(void)
{
    struct monst *guard;
    int trycount, vaultroom = (int)vault_occupied(u.urooms);
    boolean messages = TRUE;

    if (!vaultroom) {
        u.uinvault = 0;
        return;
    }

    vaultroom -= ROOMOFFSET;

    guard = findgd();
    if (++u.uinvault % 30 == 0 && !guard) {  /* if time ok and no guard now. */
        int x, y, gx, gy;
        xchar rx, ry;
        long umoney;
        const char *buf;

        /* first find the goal for the guard */
        if (!find_guard_dest(NULL, &rx, &ry))
            return;
        gx = rx;
        gy = ry;

        /* next find a good place for a door in the wall */
        x = u.ux;
        y = u.uy;
        if (level->locations[x][y].typ != ROOM) {       /* player dug a door
                                                           and is in it */
            if (level->locations[x + 1][y].typ == ROOM)
                x = x + 1;
            else if (level->locations[x][y + 1].typ == ROOM)
                y = y + 1;
            else if (level->locations[x - 1][y].typ == ROOM)
                x = x - 1;
            else if (level->locations[x][y - 1].typ == ROOM)
                y = y - 1;
            else if (level->locations[x + 1][y + 1].typ == ROOM) {
                x = x + 1;
                y = y + 1;
            } else if (level->locations[x - 1][y - 1].typ == ROOM) {
                x = x - 1;
                y = y - 1;
            } else if (level->locations[x + 1][y - 1].typ == ROOM) {
                x = x + 1;
                y = y - 1;
            } else if (level->locations[x - 1][y + 1].typ == ROOM) {
                x = x - 1;
                y = y + 1;
            }
        }
        while (level->locations[x][y].typ == ROOM) {
            int dx, dy;

            dx = (gx > x) ? 1 : (gx < x) ? -1 : 0;
            dy = (gy > y) ? 1 : (gy < y) ? -1 : 0;
            if (abs(gx - x) >= abs(gy - y))
                x += dx;
            else
                y += dy;
        }
        if (x == u.ux && y == u.uy) {
            if (level->locations[x + 1][y].typ == HWALL ||
                level->locations[x + 1][y].typ == DOOR)
                x = x + 1;
            else if (level->locations[x - 1][y].typ == HWALL ||
                     level->locations[x - 1][y].typ == DOOR)
                x = x - 1;
            else if (level->locations[x][y + 1].typ == VWALL ||
                     level->locations[x][y + 1].typ == DOOR)
                y = y + 1;
            else if (level->locations[x][y - 1].typ == VWALL ||
                     level->locations[x][y - 1].typ == DOOR)
                y = y - 1;
            else
                return;
        }

        /* make something interesting happen */
        if (!(guard = makemon(&mons[PM_GUARD], level, x, y, NO_MM_FLAGS)))
            return;
        guard->isgd = 1;
        msethostility(guard, FALSE, TRUE);
        EGD(guard)->gddone = 0;
        EGD(guard)->ogx = x;
        EGD(guard)->ogy = y;
        assign_level(&(EGD(guard)->gdlevel), &u.uz);
        EGD(guard)->vroom = vaultroom;
        EGD(guard)->warncnt = 0;

        /* We used to reset fainted status here, but that doesn't really make
           sense; instead, that's treated like normal helplessness */
        if (canspotmon(guard))
            pline("Suddenly one of the Vault's guards enters!");
        else if (canhear())
            You_hear("someone else enter the Vault.");
        else
            messages = FALSE;

        newsym(guard->mx, guard->my);
        if (youmonst.m_ap_type == M_AP_OBJECT || u.uundetected || u.uburied) {
            if (youmonst.m_ap_type == M_AP_OBJECT &&
                youmonst.mappearance != GOLD_PIECE)
                verbalize("Hey! Who left that %s in here?",
                          mimic_obj_name(&youmonst));
            /* You're mimicking some object or you're hidden. */
            if (messages)
                pline("Puzzled, %s turns around and leaves.", mhe(guard));
            mongone(guard);
            return;
        }
        if (Engulfed) {
            if ((!u.ustuck->minvis || perceives(guard->data)))
                verbalize("How did that %s get in here?", m_monnam(u.ustuck));
            if (messages)
                pline("Puzzled, %s turns around and leaves.", mhe(guard));
            mongone(guard);
            return;
        }
        if (Strangled || is_silent(youmonst.data) || u_helpless(hm_all)) {
            /* [we ought to record whether this this message has already been
               given in order to vary it upon repeat visits, but discarding the 
               monster and its egd data renders that hard] */
            verbalize("I'll be back when you're ready to speak to me!");
            mongone(guard);
            return;
        }

        action_interrupted();

        trycount = 5;
        do {
            buf = getlin("\"Hello stranger, who are you?\" -", FALSE);
            buf = msgmungspaces(buf);
        } while (!letter(buf[0]) && --trycount > 0);

        if (u.ualign.type == A_LAWFUL &&
            /* ignore trailing text, in case player includes character's rank */
            strncmpi(buf, u.uplname, (int)strlen(u.uplname)) != 0) {
            adjalign(-1);       /* Liar! */
        }

        if (!strcmpi(buf, "Croesus") || !strcmpi(buf, "Kroisos") ||
            !strcmpi(buf, "Creosote")) {
            if (!mvitals[PM_CROESUS].died) {
                verbalize("Oh, yes, of course.  Sorry to have disturbed you.");
                mongone(guard);
            } else {
                setmangry(guard);
                verbalize("Back from the dead, are you?  I'll remedy that!");
                /* don't want guard to waste next turn wielding a weapon */
                if (!MON_WEP(guard)) {
                    guard->weapon_check = NEED_HTH_WEAPON;
                    mon_wield_item(guard);
                }
            }
            return;
        }
        verbalize("I don't know you.");
        umoney = money_cnt(invent);
        if (!umoney && !hidden_gold())
            verbalize("Please follow me.");
        else {
            if (!umoney)
                verbalize("You have hidden money.");
            verbalize("Most likely all your money was stolen from this vault.");
            verbalize("Please drop that money and follow me.");
        }

        EGD(guard)->gdx = gx;
        EGD(guard)->gdy = gy;
        EGD(guard)->fcbeg = 0;
        EGD(guard)->fakecorr[0].fx = x;
        EGD(guard)->fakecorr[0].fy = y;
        if (IS_WALL(level->locations[x][y].typ))
            EGD(guard)->fakecorr[0].ftyp = level->locations[x][y].typ;
        else {  /* the initial guard location is a dug door */
            int vlt = EGD(guard)->vroom;
            xchar lowx = level->rooms[vlt].lx, hix = level->rooms[vlt].hx;
            xchar lowy = level->rooms[vlt].ly, hiy = level->rooms[vlt].hy;

            if (x == lowx - 1 && y == lowy - 1)
                EGD(guard)->fakecorr[0].ftyp = TLCORNER;
            else if (x == hix + 1 && y == lowy - 1)
                EGD(guard)->fakecorr[0].ftyp = TRCORNER;
            else if (x == lowx - 1 && y == hiy + 1)
                EGD(guard)->fakecorr[0].ftyp = BLCORNER;
            else if (x == hix + 1 && y == hiy + 1)
                EGD(guard)->fakecorr[0].ftyp = BRCORNER;
            else if (y == lowy - 1 || y == hiy + 1)
                EGD(guard)->fakecorr[0].ftyp = HWALL;
            else if (x == lowx - 1 || x == hix + 1)
                EGD(guard)->fakecorr[0].ftyp = VWALL;
        }
        level->locations[x][y].typ = DOOR;
        level->locations[x][y].doormask = D_NODOOR;
        unblock_point(x, y);    /* doesn't block light */
        EGD(guard)->fcend = 1;
        EGD(guard)->warncnt = 1;
    }
}
예제 #13
0
/* Returns 1 when something was stolen (or at least, when N should flee now)
 * Returns -1 if the monster died in the attempt
 * Avoid stealing the object stealoid
 */
int
steal(struct monst *mtmp, const char **objnambuf)
{
    struct obj *otmp;
    int tmp, could_petrify, named = 0, armordelay, slowly = 0;
    boolean monkey_business;    /* true iff an animal is doing the thievery */

    if (objnambuf)
        *objnambuf = "";
    /* the following is true if successful on first of two attacks. */
    if (!monnear(mtmp, u.ux, u.uy))
        return 0;

    if (!invent || (inv_cnt(FALSE) == 1 && uskin())) {
    nothing_to_steal:
        /* Not even a thousand men in armor can strip a naked man. */
        if (Blind)
            pline("Somebody tries to rob you, but finds nothing to steal.");
        else
            pline("%s tries to rob you, but there is nothing to steal!",
                  Monnam(mtmp));
        return 1;       /* let her flee */
    }

    monkey_business = is_animal(mtmp->data);
    if (monkey_business) {
        ;       /* skip ring special cases */
    } else if (Adornment & W_MASK(os_ringl)) {
        otmp = uleft;
        goto gotobj;
    } else if (Adornment & W_MASK(os_ringr)) {
        otmp = uright;
        goto gotobj;
    }

    tmp = 0;
    for (otmp = invent; otmp; otmp = otmp->nobj)
        if ((!uarm || otmp != uarmc) && otmp != uskin()
#ifdef INVISIBLE_OBJECTS
            && (!otmp->oinvis || perceives(mtmp->data))
#endif
            )
            tmp += ((otmp->owornmask & W_WORN) ? 5 : 1);
    if (!tmp)
        goto nothing_to_steal;
    tmp = rn2(tmp);
    for (otmp = invent; otmp; otmp = otmp->nobj)
        if ((!uarm || otmp != uarmc) && otmp != uskin()
#ifdef INVISIBLE_OBJECTS
            && (!otmp->oinvis || perceives(mtmp->data))
#endif
            )
            if ((tmp -= ((otmp->owornmask & W_WORN) ? 5 : 1)) < 0)
                break;
    if (!otmp) {
        impossible("Steal fails!");
        return 0;
    }
    /* can't steal gloves while wielding - so steal the wielded item. */
    if (otmp == uarmg && uwep)
        otmp = uwep;
    /* can't steal armor while wearing cloak - so steal the cloak. */
    else if (otmp == uarm && uarmc)
        otmp = uarmc;
    else if (otmp == uarmu && uarmc)
        otmp = uarmc;
    else if (otmp == uarmu && uarm && !uskin())
        otmp = uarm;
gotobj:

    /* animals can't overcome curse stickiness nor unlock chains */
    if (monkey_business) {
        boolean ostuck;

        /* is the player prevented from voluntarily giving up this item?
           (ignores loadstones; the !can_carry() check will catch those) */
        if (otmp == uball)
            ostuck = TRUE;      /* effectively worn; curse is implicit */
        else if (otmp == uquiver || (otmp == uswapwep && !u.twoweap))
            ostuck = FALSE;     /* not really worn; curse doesn't matter */
        else
            ostuck = (otmp->cursed && otmp->owornmask);

        if (ostuck || !can_carry(mtmp, otmp)) {
            static const char *const how[] =
                { "steal", "snatch", "grab", "take" };
        cant_take:
            pline("%s tries to %s your %s but gives up.", Monnam(mtmp),
                  how[rn2(SIZE(how))],
                  (otmp->owornmask & W_ARMOR) ? equipname(otmp) : cxname(otmp));
            /* the fewer items you have, the less likely the thief is going to
               stick around to try again (0) instead of running away (1) */
            return !rn2(inv_cnt(FALSE) / 5 + 2);
        }
    }

    if (otmp->otyp == LEASH && otmp->leashmon) {
        if (monkey_business && otmp->cursed)
            goto cant_take;
        o_unleash(otmp);
    }

    /* you're going to notice the theft... */
    action_interrupted();

    if (otmp->owornmask & W_WORN) {
        switch (otmp->oclass) {
        case TOOL_CLASS:
        case AMULET_CLASS:
        case RING_CLASS:
        case FOOD_CLASS:       /* meat ring */
            remove_worn_item(otmp, TRUE);
            break;
        case ARMOR_CLASS:
            armordelay = objects[otmp->otyp].oc_delay;
            if (monkey_business) {
                /* animals usually don't have enough patience to take off items 
                   which require extra time */
                if (armordelay >= 1 && rn2(10))
                    goto cant_take;
                remove_worn_item(otmp, TRUE);
                break;
            } else {
                int curssv = otmp->cursed;
                boolean seen = canspotmon(mtmp);

                otmp->cursed = 0;
                /* can't charm you without first waking you */
                cancel_helplessness(hm_fainted, "Someone revives you.");
                slowly = (armordelay >= 1 || u_helpless(hm_all));
                if (u_helpless(hm_all)) {
                    pline("%s tries to %s you, but is dismayed by your lack of "
                          "response.", !seen ? "She" : Monnam(mtmp),
                          u.ufemale ? "charm" : "seduce");
                    return (0);
                }
                if (u.ufemale)
                    pline("%s charms you.  You gladly %s your %s.",
                          !seen ? "She" : Monnam(mtmp),
                          curssv ? "let her take" : slowly ? "start removing" :
                          "hand over", equipname(otmp));
                else
                    pline("%s seduces you and %s off your %s.",
                          !seen ? "She" : Adjmonnam(mtmp, "beautiful"),
                          curssv ? "helps you to take" : slowly ?
                          "you start taking" : "you take", equipname(otmp));
                named++;
                if (armordelay)
                    helpless(armordelay, hr_busy, "taking off clothes",
                             "You finish disrobing.");
                remove_worn_item(otmp, TRUE);
                otmp->cursed = curssv;
                /* Note: it used to be that the nymph would wait for you to
                   disrobe, then take the item, but that lead to huge
                   complications in the code (and a rather unfun situation where
                   the nymph could chain armor theft), and some resulting
                   bugs. Instead, we just go down the normal codepath; you lose
                   the item, and you're left helpless for the length of time it
                   should have taken to remove. The nymph will stay around (due
                   to the slowly || u_helpless(hm_all) check at the end of the
                   function). */
            }
            break;
        default:
            impossible("Tried to steal a strange worn thing. [%d]",
                       otmp->oclass);
        }
    } else if (otmp->owornmask)
        remove_worn_item(otmp, TRUE);

    /* do this before removing it from inventory */
    if (objnambuf)
        *objnambuf = yname(otmp);
    /* set mavenge bit so knights won't suffer an alignment penalty during
       retaliation; */
    mtmp->mavenge = 1;

    freeinv(otmp);
    pline("%s stole %s.", named ? "She" : Monnam(mtmp), doname(otmp));
    could_petrify = (otmp->otyp == CORPSE &&
                     touch_petrifies(&mons[otmp->corpsenm]));
    mpickobj(mtmp, otmp);       /* may free otmp */
    if (could_petrify && !(mtmp->misc_worn_check & W_MASK(os_armg))) {
        minstapetrify(mtmp, TRUE);
        return -1;
    }
    return (slowly || u_helpless(hm_all)) ? 0 : 1;
}