/* 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); } }
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; }
/* 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); } }
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; }
/* 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); } }
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(); }
/* 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; }
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(); } } }
/* 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(); }
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)); } }
/* 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; }
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; } }
/* 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; }