int FDECL()

in src/zap.c [3221:3513]


int FDECL((*fhitm), (MONST_P, OBJ_P)), /* fns called when mon/obj hit */
    FDECL((*fhito), (OBJ_P, OBJ_P));
struct obj **pobj; /* object tossed/used, set to NULL
                    * if object is destroyed */
{
    struct monst *mtmp, *result = (struct monst *) 0;
    struct obj *obj = *pobj;
    uchar typ;
    boolean shopdoor = FALSE, point_blank = TRUE;
    boolean in_skip = FALSE, allow_skip = FALSE;
    boolean tethered_weapon = FALSE;
    int skiprange_start = 0, skiprange_end = 0, skipcount = 0;

    if (weapon == KICKED_WEAPON) {
        /* object starts one square in front of player */
        bhitpos.x = u.ux + ddx;
        bhitpos.y = u.uy + ddy;
        range--;
    } else {
        bhitpos.x = u.ux;
        bhitpos.y = u.uy;
    }

    if (weapon == THROWN_WEAPON && obj && obj->otyp == ROCK) {
        skiprange(range, &skiprange_start, &skiprange_end);
        allow_skip = !rn2(3);
    }

    if (weapon == FLASHED_LIGHT) {
        tmp_at(DISP_BEAM, cmap_to_glyph(S_flashbeam));
    } else if (weapon == THROWN_TETHERED_WEAPON && obj) {
        tethered_weapon = TRUE;
        weapon = THROWN_WEAPON; /* simplify 'if's that follow below */
        tmp_at(DISP_TETHER, obj_to_glyph(obj, rn2_on_display_rng));
    } else if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM)
        tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng));

    while (range-- > 0) {
        int x, y;

        bhitpos.x += ddx;
        bhitpos.y += ddy;
        x = bhitpos.x;
        y = bhitpos.y;

        if (!isok(x, y)) {
            bhitpos.x -= ddx;
            bhitpos.y -= ddy;
            break;
        }

        if (is_pick(obj) && inside_shop(x, y)
            && (mtmp = shkcatch(obj, x, y)) != 0) {
            tmp_at(DISP_END, 0);
            result = mtmp;
            goto bhit_done;
        }

        typ = levl[bhitpos.x][bhitpos.y].typ;

        /* iron bars will block anything big enough and break some things */
        if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) {
            if (typ == IRONBARS
                && hits_bars(pobj, x - ddx, y - ddy, bhitpos.x, bhitpos.y,
                             point_blank ? 0 : !rn2(5), 1)) {
                /* caveat: obj might now be null... */
                obj = *pobj;
                bhitpos.x -= ddx;
                bhitpos.y -= ddy;
                break;
            } else if (obj->lamplit && !Blind) {
                show_transient_light(obj, bhitpos.x, bhitpos.y);
            }
        }

        if (weapon == ZAPPED_WAND && find_drawbridge(&x, &y)) {
            boolean learn_it = FALSE;

            switch (obj->otyp) {
            case WAN_OPENING:
            case SPE_KNOCK:
                if (is_db_wall(bhitpos.x, bhitpos.y)) {
                    if (cansee(x, y) || cansee(bhitpos.x, bhitpos.y))
                        learn_it = TRUE;
                    open_drawbridge(x, y);
                }
                break;
            case WAN_LOCKING:
            case SPE_WIZARD_LOCK:
                if ((cansee(x, y) || cansee(bhitpos.x, bhitpos.y))
                    && levl[x][y].typ == DRAWBRIDGE_DOWN)
                    learn_it = TRUE;
                close_drawbridge(x, y);
                break;
            case WAN_STRIKING:
            case SPE_FORCE_BOLT:
                if (typ != DRAWBRIDGE_UP)
                    destroy_drawbridge(x, y);
                learn_it = TRUE;
                break;
            }
            if (learn_it)
                learnwand(obj);
        }

        mtmp = m_at(bhitpos.x, bhitpos.y);

        /*
         * skipping rocks
         *
         * skiprange_start is only set if this is a thrown rock
         */
        if (skiprange_start && (range == skiprange_start) && allow_skip) {
            if (is_pool(bhitpos.x, bhitpos.y) && !mtmp) {
                in_skip = TRUE;
                if (!Blind)
                    pline("%s %s%s.", Yname2(obj), otense(obj, "skip"),
                          skipcount ? " again" : "");
                else
                    You_hear("%s skip.", yname(obj));
                skipcount++;
            } else if (skiprange_start > skiprange_end + 1) {
                --skiprange_start;
            }
        }
        if (in_skip) {
            if (range <= skiprange_end) {
                in_skip = FALSE;
                if (range > 3) /* another bounce? */
                    skiprange(range, &skiprange_start, &skiprange_end);
            } else if (mtmp && M_IN_WATER(mtmp->data)) {
                if (!Blind && canspotmon(mtmp))
                    pline("%s %s over %s.", Yname2(obj), otense(obj, "pass"),
                          mon_nam(mtmp));
                mtmp = (struct monst *) 0;
            }
        }

        /* if mtmp is a shade and missile passes harmlessly through it,
           give message and skip it in order to keep going */
        if (mtmp && (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON)
            && shade_miss(&youmonst, mtmp, obj, TRUE, TRUE))
            mtmp = (struct monst *) 0;

        if (mtmp) {
            notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my);
            if (weapon == FLASHED_LIGHT) {
                /* FLASHED_LIGHT hitting invisible monster should
                   pass through instead of stop so we call
                   flash_hits_mon() directly rather than returning
                   mtmp back to caller.  That allows the flash to
                   keep on going.  Note that we use mtmp->minvis
                   not canspotmon() because it makes no difference
                   whether the hero can see the monster or not. */
                if (mtmp->minvis) {
                    obj->ox = u.ux, obj->oy = u.uy;
                    (void) flash_hits_mon(mtmp, obj);
                } else {
                    tmp_at(DISP_END, 0);
                    result = mtmp; /* caller will call flash_hits_mon */
                    goto bhit_done;
                }
            } else if (weapon == INVIS_BEAM) {
                /* Like FLASHED_LIGHT, INVIS_BEAM should continue
                   through invisible targets; unlike it, we aren't
                   prepared for multiple hits so just get first one
                   that's either visible or could see its invisible
                   self.  [No tmp_at() cleanup is needed here.] */
                if (!mtmp->minvis || perceives(mtmp->data)) {
                    result = mtmp;
                    goto bhit_done;
                }
            } else if (weapon != ZAPPED_WAND) {

                /* THROWN_WEAPON, KICKED_WEAPON */
                if (!tethered_weapon)
                    tmp_at(DISP_END, 0);

                if (cansee(bhitpos.x, bhitpos.y) && !canspotmon(mtmp))
                    map_invisible(bhitpos.x, bhitpos.y);
                result = mtmp;
                goto bhit_done;
            } else {
                /* ZAPPED_WAND */
                (*fhitm)(mtmp, obj);
                range -= 3;
            }
        } else {
            if (weapon == ZAPPED_WAND && obj->otyp == WAN_PROBING
                && glyph_is_invisible(levl[bhitpos.x][bhitpos.y].glyph)) {
                unmap_object(bhitpos.x, bhitpos.y);
                newsym(x, y);
            }
        }
        if (fhito) {
            if (bhitpile(obj, fhito, bhitpos.x, bhitpos.y, 0))
                range--;
        } else {
            if (weapon == KICKED_WEAPON
                && ((obj->oclass == COIN_CLASS
                     && OBJ_AT(bhitpos.x, bhitpos.y))
                    || ship_object(obj, bhitpos.x, bhitpos.y,
                                   costly_spot(bhitpos.x, bhitpos.y)))) {
                tmp_at(DISP_END, 0);
                goto bhit_done; /* result == (struct monst *) 0 */
            }
        }
        if (weapon == ZAPPED_WAND && (IS_DOOR(typ) || typ == SDOOR)) {
            switch (obj->otyp) {
            case WAN_OPENING:
            case WAN_LOCKING:
            case WAN_STRIKING:
            case SPE_KNOCK:
            case SPE_WIZARD_LOCK:
            case SPE_FORCE_BOLT:
                if (doorlock(obj, bhitpos.x, bhitpos.y)) {
                    if (cansee(bhitpos.x, bhitpos.y)
                        || (obj->otyp == WAN_STRIKING && !Deaf))
                        learnwand(obj);
                    if (levl[bhitpos.x][bhitpos.y].doormask == D_BROKEN
                        && *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE)) {
                        shopdoor = TRUE;
                        add_damage(bhitpos.x, bhitpos.y, SHOP_DOOR_COST);
                    }
                }
                break;
            }
        }
        if (!ZAP_POS(typ) || closed_door(bhitpos.x, bhitpos.y)) {
            bhitpos.x -= ddx;
            bhitpos.y -= ddy;
            break;
        }
        if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM) {
            /* 'I' present but no monster: erase */
            /* do this before the tmp_at() */
            if (glyph_is_invisible(levl[bhitpos.x][bhitpos.y].glyph)
                && cansee(x, y)) {
                unmap_object(bhitpos.x, bhitpos.y);
                newsym(x, y);
            }
            tmp_at(bhitpos.x, bhitpos.y);
            delay_output();
            /* kicked objects fall in pools */
            if ((weapon == KICKED_WEAPON)
                && (is_pool(bhitpos.x, bhitpos.y)
                    || is_lava(bhitpos.x, bhitpos.y)))
                break;
            if (IS_SINK(typ) && weapon != FLASHED_LIGHT)
                break; /* physical objects fall onto sink */
        }
        /* limit range of ball so hero won't make an invalid move */
        if (weapon == THROWN_WEAPON && range > 0
            && obj->otyp == HEAVY_IRON_BALL) {
            struct obj *bobj;
            struct trap *t;

            if ((bobj = sobj_at(BOULDER, x, y)) != 0) {
                if (cansee(x, y))
                    pline("%s hits %s.", The(distant_name(obj, xname)),
                          an(xname(bobj)));
                range = 0;
            } else if (obj == uball) {
                if (!test_move(x - ddx, y - ddy, ddx, ddy, TEST_MOVE)) {
                    /* nb: it didn't hit anything directly */
                    if (cansee(x, y))
                        pline("%s jerks to an abrupt halt.",
                              The(distant_name(obj, xname))); /* lame */
                    range = 0;
                } else if (Sokoban && (t = t_at(x, y)) != 0
                           && (is_pit(t->ttyp) || is_hole(t->ttyp))) {
                    /* hero falls into the trap, so ball stops */
                    range = 0;
                }
            }
        }

        /* thrown/kicked missile has moved away from its starting spot */
        point_blank = FALSE; /* affects passing through iron bars */
    }

    if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM && !tethered_weapon)
        tmp_at(DISP_END, 0);

    if (shopdoor)
        pay_for_damage("destroy", FALSE);

 bhit_done:
    if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON)
        transient_light_cleanup();

    return result;
}