/* SCCS Id: @(#)dokick.c 3.3 2000/04/21 */ /* Copyright (c) Izchak Miller, Mike Stephenson, Steve Linhart, 1989. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #include "eshk.h" #define is_bigfoot(x) ((x) == &mons[PM_SASQUATCH]) #define martial() (martial_bonus() || is_bigfoot(youmonst.data) || \ (uarmf && uarmf->otyp == KICKING_BOOTS)) static NEARDATA struct rm *maploc; static NEARDATA const char *gate_str; extern boolean notonhead; /* for long worms */ STATIC_DCL void FDECL(kickdmg, (struct monst *, BOOLEAN_P)); STATIC_DCL void FDECL(kick_monster, (XCHAR_P, XCHAR_P)); STATIC_DCL int FDECL(kick_object, (XCHAR_P, XCHAR_P)); STATIC_DCL char *FDECL(kickstr, (char *)); STATIC_DCL void FDECL(otransit_msg, (struct obj *, BOOLEAN_P, long)); STATIC_DCL void FDECL(drop_to, (coord *,SCHAR_P)); static NEARDATA struct obj *kickobj; #define IS_SHOP(x) (rooms[x].rtype >= SHOPBASE) static const char kick_passes_thru[] = "kick passes harmlessly through"; STATIC_OVL void kickdmg(mon, clumsy) register struct monst *mon; register boolean clumsy; { register int mdx, mdy; register int dmg = ( ACURRSTR + ACURR(A_DEX) + ACURR(A_CON) )/ 15; int kick_skill = P_NONE; int blessed_foot_damage = 0; if (uarmf && uarmf->otyp == KICKING_BOOTS) dmg += 5; /* excessive wt affects dex, so it affects dmg */ if (clumsy) dmg /= 2; /* kicking a dragon or an elephant will not harm it */ if (thick_skinned(mon->data)) dmg = 0; /* attacking a shade is useless */ if (mon->data == &mons[PM_SHADE]) dmg = 0; if ((is_undead(mon->data) || is_demon(mon->data)) && uarmf && uarmf->blessed) blessed_foot_damage = 1; if (mon->data == &mons[PM_SHADE] && !blessed_foot_damage) { pline_The("%s.", kick_passes_thru); /* doesn't exercise skill or abuse alignment or frighten pet, and shades have no passive counterattack */ return; } if(mon->m_ap_type) seemimic(mon); /* it is unchivalrous to attack the defenseless or from behind */ if (Role_if(PM_KNIGHT) && u.ualign.type == A_LAWFUL && u.ualign.record > -10 && (!mon->mcanmove || mon->msleeping || mon->mflee)) { You_feel("like a caitiff!"); adjalign(-1); } /* squeeze some guilt feelings... */ if(mon->mtame) { abuse_dog(mon); mon->mflee = mon->mtame ? 1 : 0; #ifdef HISX mon->mfleetim = mon->mfleetim + (dmg ? rnd(dmg) : 1); #else mon->mfleetim += (dmg ? rnd(dmg) : 1); #endif } if (dmg > 0) { /* convert potential damage to actual damage */ dmg = rnd(dmg); if (martial()) { if (dmg > 1) kick_skill = P_MARTIAL_ARTS; dmg += rn2(ACURR(A_DEX)/2 + 1); } /* a good kick exercises your dex */ exercise(A_DEX, TRUE); } if (blessed_foot_damage) dmg += rnd(4); if (uarmf) dmg += uarmf->spe; dmg += u.udaminc; /* add ring(s) of increase damage */ if (dmg > 0) mon->mhp -= dmg; if(mon->mhp > 0 && martial() && !bigmonst(mon->data) && !rn2(3) && mon->mcanmove && mon != u.ustuck) { /* see if the monster has a place to move into */ mdx = mon->mx + u.dx; mdy = mon->my + u.dy; if(goodpos(mdx, mdy, mon)) { pline("%s reels from the blow.", Monnam(mon)); if (!m_in_out_region(mon, mdx, mdy)) { remove_monster(mon->mx, mon->my); newsym(mon->mx, mon->my); place_monster(mon, mdx, mdy); newsym(mon->mx, mon->my); set_apparxy(mon); } } } (void) passive(mon, TRUE, mon->mhp > 0, AT_KICK); if (mon->mhp <= 0) killed(mon); /* may bring up a dialog, so put this after all messages */ if (kick_skill != P_NONE) /* exercise proficiency */ use_skill(kick_skill, 1); } STATIC_OVL void kick_monster(x, y) register xchar x, y; { register boolean clumsy = FALSE; register struct monst *mon = m_at(x, y); register int i, j; bhitpos.x = x; bhitpos.y = y; if (attack_checks(mon, (struct obj *)0)) return; setmangry(mon); /* Kick attacks by kicking monsters are normal attacks, not special. * This is almost always worthless, since you can either take one turn * and do all your kicks, or else take one turn and attack the monster * normally, getting all your attacks _including_ all your kicks. * If you have >1 kick attack, you get all of them. */ if (Upolyd && attacktype(youmonst.data, AT_KICK)) { struct attack *uattk; int sum; schar tmp = find_roll_to_hit(mon); for (i = 0; i < NATTK; i++) { /* first of two kicks might have provoked counterattack that has incapacitated the hero (ie, floating eye) */ if (multi < 0) break; uattk = &youmonst.data->mattk[i]; /* we only care about kicking attacks here */ if (uattk->aatyp != AT_KICK) continue; if (mon->data == &mons[PM_SHADE] && (!uarmf || !uarmf->blessed)) { /* doesn't matter whether it would have hit or missed, and shades have no passive counterattack */ Your("%s %s.", kick_passes_thru, mon_nam(mon)); break; /* skip any additional kicks */ } else if (tmp > rnd(20)) { You("kick %s.", mon_nam(mon)); sum = damageum(mon, uattk); (void)passive(mon, (boolean)(sum > 0), (sum != 2), AT_KICK); } else { missum(mon, uattk); (void)passive(mon, 0, 1, AT_KICK); } } return; } if(Levitation && !rn2(3) && verysmall(mon->data) && !is_flyer(mon->data)) { pline("Floating in the air, you miss wildly!"); exercise(A_DEX, FALSE); (void) passive(mon, FALSE, 1, AT_KICK); return; } i = -inv_weight(); j = weight_cap(); if(i < (j*3)/10) { if(!rn2((i < j/10) ? 2 : (i < j/5) ? 3 : 4)) { if(martial() && !rn2(2)) goto doit; Your("clumsy kick does no damage."); (void) passive(mon, FALSE, 1, AT_KICK); return; } if(i < j/10) clumsy = TRUE; else if(!rn2((i < j/5) ? 2 : 3)) clumsy = TRUE; } if(Fumbling) clumsy = TRUE; else if(uarm && objects[uarm->otyp].oc_bulky && ACURR(A_DEX) < rnd(25)) clumsy = TRUE; doit: You("kick %s.", mon_nam(mon)); if(!rn2(clumsy ? 3 : 4) && (clumsy || !bigmonst(mon->data)) && mon->mcansee && !mon->mtrapped && !thick_skinned(mon->data) && mon->data->mlet != S_EEL && haseyes(mon->data) && mon->mcanmove && !mon->mstun && !mon->mconf && !mon->msleeping && mon->data->mmove >= 12) { if(!nohands(mon->data) && !rn2(martial() ? 5 : 3)) { pline("%s blocks your %skick.", Monnam(mon), clumsy ? "clumsy " : ""); (void) passive(mon, FALSE, 1, AT_KICK); return; } else { mnexto(mon); if(mon->mx != x || mon->my != y) { pline("%s %s, %s evading your %skick.", Monnam(mon), (can_teleport(mon->data) ? "teleports" : is_floater(mon->data) ? "floats" : is_flyer(mon->data) ? "flutters" : (nolimbs(mon->data) || slithy(mon->data)) ? "slides" : "jumps"), clumsy ? "easily" : "nimbly", clumsy ? "clumsy " : ""); (void) passive(mon, FALSE, 1, AT_KICK); return; } } } kickdmg(mon, clumsy); } /* * Return TRUE if caught (the gold taken care of), FALSE otherwise. * The gold object is *not* attached to the fobj chain! */ boolean ghitm(mtmp, gold) register struct monst *mtmp; register struct obj *gold; { if(!likes_gold(mtmp->data) && !mtmp->isshk && !mtmp->ispriest && !is_mercenary(mtmp->data)) { wakeup(mtmp); } else if (!mtmp->mcanmove) { /* too light to do real damage */ if (canseemon(mtmp)) pline_The("gold hits %s.", mon_nam(mtmp)); } else { mtmp->msleeping = 0; mtmp->meating = 0; if(!rn2(4)) setmangry(mtmp); /* not always pleasing */ /* greedy monsters catch gold */ if (cansee(mtmp->mx, mtmp->my)) pline("%s catches the gold.", Monnam(mtmp)); mtmp->mgold += gold->quan; if (mtmp->isshk) { long robbed = ESHK(mtmp)->robbed; if (robbed) { robbed -= gold->quan; if (robbed < 0) robbed = 0; pline_The("amount %scovers %s recent losses.", !robbed ? "" : "partially ", his[mtmp->female]); ESHK(mtmp)->robbed = robbed; if(!robbed) make_happy_shk(mtmp, FALSE); } else { if(mtmp->mpeaceful) { ESHK(mtmp)->credit += gold->quan; You("have %ld zorkmid%s in credit.", ESHK(mtmp)->credit, plur(ESHK(mtmp)->credit)); } else verbalize("Thanks, scum!"); } } else if (mtmp->ispriest) { if (mtmp->mpeaceful) verbalize("Thank you for your contribution."); else verbalize("Thanks, scum!"); } else if (is_mercenary(mtmp->data)) { long goldreqd = 0L; if (rn2(3)) { if (mtmp->data == &mons[PM_SOLDIER]) goldreqd = 100L; else if (mtmp->data == &mons[PM_SERGEANT]) goldreqd = 250L; else if (mtmp->data == &mons[PM_LIEUTENANT]) goldreqd = 500L; else if (mtmp->data == &mons[PM_CAPTAIN]) goldreqd = 750L; if (goldreqd) { if (gold->quan > goldreqd + (u.ugold + u.ulevel*rn2(5))/ACURR(A_CHA)) mtmp->mpeaceful = TRUE; } } if (mtmp->mpeaceful) verbalize("That should do. Now beat it!"); else verbalize("That's not enough, coward!"); } dealloc_obj(gold); return(1); } return(0); } STATIC_OVL int kick_object(x, y) xchar x, y; { int range; register struct monst *mon, *shkp; register struct obj *otmp; struct trap *trap; char bhitroom; boolean costly, insider, isgold, slide = FALSE; /* if a pile, the "top" object gets kicked */ kickobj = level.objects[x][y]; /* kickobj should always be set due to conditions of call */ if(!kickobj || kickobj->otyp == BOULDER || kickobj == uball || kickobj == uchain) return(0); if ((trap = t_at(x,y)) != 0 && (((trap->ttyp == PIT || trap->ttyp == SPIKED_PIT) && !Passes_walls) || trap->ttyp == WEB)) { if (!trap->tseen) find_trap(trap); You_cant("kick %s that's in a %s!", something, Hallucination ? "tizzy" : (trap->ttyp == WEB) ? "web" : "pit"); return 1; } if(Fumbling && !rn2(3)) { Your("clumsy kick missed."); return(1); } if(kickobj->otyp == CORPSE && touch_petrifies(&mons[kickobj->corpsenm]) && !Stone_resistance && !uarmf) { char kbuf[BUFSZ]; You("kick the %s corpse with your bare %s.", mons[kickobj->corpsenm].mname, makeplural(body_part(FOOT))); if (!(poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))) { You("turn to stone..."); killer_format = KILLED_BY; /* KMH -- otmp should be kickobj */ Sprintf(kbuf, "kicking a %s corpse without boots", mons[kickobj->corpsenm].mname); killer = kbuf; done(STONING); } } /* range < 2 means the object will not move. */ /* maybe dexterity should also figure here. */ range = (int)((ACURRSTR)/2 - kickobj->owt/40); if(martial()) range += rnd(3); if (is_pool(x, y)) { /* you're in the water too; significantly reduce range */ range = range / 3 + 1; /* {1,2}=>1, {3,4,5}=>2, {6,7,8}=>3 */ } else { if (is_ice(x, y)) range += rnd(3), slide = TRUE; if (kickobj->greased) range += rnd(3), slide = TRUE; } /* Mjollnir is magically too heavy to kick */ if(kickobj->oartifact == ART_MJOLLNIR) range = 1; /* see if the object has a place to move into */ if(!ZAP_POS(levl[x+u.dx][y+u.dy].typ) || closed_door(x+u.dx, y+u.dy)) range = 1; costly = ((shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) && costly_spot(x, y)); insider = (*u.ushops && inside_shop(u.ux, u.uy) && *in_rooms(x, y, SHOPBASE) == *u.ushops); /* a box gets a chance of breaking open here */ if(Is_box(kickobj)) { boolean otrp = kickobj->otrapped; struct obj *otmp2; long loss = 0L; if(range < 2) pline("THUD!"); for(otmp = kickobj->cobj; otmp; otmp = otmp2) { const char *result = (char *)0; otmp2 = otmp->nobj; if (objects[otmp->otyp].oc_material == GLASS && otmp->oclass != GEM_CLASS && !obj_resists(otmp, 33, 100)) { result = "shatter"; } else if (otmp->otyp == EGG && !rn2(3)) { result = "cracking"; } if (result) { You_hear("a muffled %s.",result); if(costly) loss += stolen_value(otmp, x, y, (boolean)shkp->mpeaceful, TRUE); if (otmp->quan > 1L) useup(otmp); else { obj_extract_self(otmp); obfree(otmp, (struct obj *) 0); } } } if(costly && loss) { if(!insider) { You("caused %ld zorkmids worth of damage!", loss); make_angry_shk(shkp, x, y); } else { You("owe %s %ld zorkmids for objects destroyed.", mon_nam(shkp), loss); } } if (kickobj->olocked) { if (!rn2(5) || (martial() && !rn2(2))) { You("break open the lock!"); kickobj->olocked = 0; kickobj->obroken = 1; if (otrp) (void) chest_trap(kickobj, LEG, FALSE); return(1); } } else { if (!rn2(3) || (martial() && !rn2(2))) { pline_The("lid slams open, then falls shut."); if (otrp) (void) chest_trap(kickobj, LEG, FALSE); return(1); } } if(range < 2) return(1); /* else let it fall through to the next cases... */ } /* fragile objects should not be kicked */ if (hero_breaks(kickobj, kickobj->ox, kickobj->oy, FALSE)) return 1; if (IS_ROCK(levl[x][y].typ) || closed_door(x, y)) { if ((!martial() && rn2(20) > ACURR(A_DEX)) || IS_ROCK(levl[u.ux][u.uy].typ) || closed_door(u.ux, u.uy)) { if (Blind) pline("It doesn't come loose."); else pline("%s do%sn't come loose.", The(distant_name(kickobj, xname)), (kickobj->quan == 1L) ? "es" : ""); return(!rn2(3) || martial()); } if (Blind) pline("It comes loose."); else pline("%s come%s loose.", The(distant_name(kickobj, xname)), (kickobj->quan == 1L) ? "s" : ""); obj_extract_self(kickobj); newsym(x, y); if (costly && (!costly_spot(u.ux, u.uy) || !index(u.urooms, *in_rooms(x, y, SHOPBASE)))) addtobill(kickobj, FALSE, FALSE, FALSE); if(!flooreffects(kickobj,u.ux,u.uy,"fall")) { place_object(kickobj, u.ux, u.uy); stackobj(kickobj); newsym(u.ux, u.uy); } return(1); } isgold = (kickobj->oclass == GOLD_CLASS); /* too heavy to move. range is calculated as potential distance from * player, so range == 2 means the object may move up to one square * from its current position */ if(range < 2 || (isgold && kickobj->quan > 300L)) { if(!Is_box(kickobj)) pline("Thump!"); return(!rn2(3) || martial()); } if (kickobj->quan > 1L && !isgold) (void) splitobj(kickobj, 1L); if (slide && !Blind) pline("Whee! %s slide%s across the %s.", Doname2(kickobj), kickobj->quan > 1L ? "" : "s", surface(x,y)); obj_extract_self(kickobj); (void) snuff_candle(kickobj); newsym(x, y); mon = bhit(u.dx, u.dy, range, KICKED_WEAPON, (int FDECL((*),(MONST_P,OBJ_P)))0, (int FDECL((*),(OBJ_P,OBJ_P)))0, kickobj); if(mon) { if (mon->isshk && kickobj->where == OBJ_MINVENT && kickobj->ocarry == mon) return 1; /* alert shk caught it */ notonhead = (mon->mx != bhitpos.x || mon->my != bhitpos.y); if (isgold ? ghitm(mon, kickobj) : /* caught? */ thitmonst(mon, kickobj)) /* hit && used up? */ return(1); } /* the object might have fallen down a hole */ if (kickobj->where == OBJ_MIGRATING) return 1; bhitroom = *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE); if (costly && (!costly_spot(bhitpos.x, bhitpos.y) || *in_rooms(x, y, SHOPBASE) != bhitroom)) { if(isgold) costly_gold(x, y, kickobj->quan); else (void)stolen_value(kickobj, x, y, (boolean)shkp->mpeaceful, FALSE); } if(flooreffects(kickobj,bhitpos.x,bhitpos.y,"fall")) return(1); place_object(kickobj, bhitpos.x, bhitpos.y); stackobj(kickobj); newsym(kickobj->ox, kickobj->oy); return(1); } STATIC_OVL char * kickstr(buf) char *buf; { const char *what; if (kickobj) what = distant_name(kickobj,doname); else if (IS_DOOR(maploc->typ)) what = "a door"; else if (IS_STWALL(maploc->typ)) what = "a wall"; else if (IS_ROCK(maploc->typ)) what = "a rock"; else if (IS_THRONE(maploc->typ)) what = "a throne"; else if (IS_FOUNTAIN(maploc->typ)) what = "a fountain"; else if (IS_GRAVE(maploc->typ)) what = "a headstone"; else if (IS_TREE(maploc->typ)) what = "a tree"; #ifdef SINKS else if (IS_SINK(maploc->typ)) what = "a sink"; #endif else if (IS_ALTAR(maploc->typ)) what = "an altar"; else if (IS_DRAWBRIDGE(maploc->typ)) what = "the drawbridge"; else if (maploc->typ == STAIRS) what = "the stairs"; else if (maploc->typ == LADDER) what = "a ladder"; else if (maploc->typ == IRONBARS) what = "an iron bar"; else what = "something weird"; return strcat(strcpy(buf, "kicking "), what); } int dokick() { register int x, y; int avrg_attrib; register struct monst *mtmp; s_level *slev; boolean no_kick = FALSE; char buf[BUFSZ]; if (nolimbs(youmonst.data) || slithy(youmonst.data)) { You("have no legs to kick with."); no_kick = TRUE; } else if (verysmall(youmonst.data)) { You("are too small to do any kicking."); no_kick = TRUE; #ifdef STEED } else if (u.usteed) { if (yn_function("Kick your steed?", ynchars, 'y') == 'y') { You("kick %s.", mon_nam(u.usteed)); kick_steed(); return 1; } else { return 0; } #endif } else if (Wounded_legs) { /* note: jump() has similar code */ long wl = (EWounded_legs & BOTH_SIDES); const char *bp = body_part(LEG); if (wl == BOTH_SIDES) bp = makeplural(bp); Your("%s%s %s in no shape for kicking.", (wl == LEFT_SIDE) ? "left " : (wl == RIGHT_SIDE) ? "right " : "", bp, (wl == BOTH_SIDES) ? "are" : "is"); no_kick = TRUE; } else if (near_capacity() > SLT_ENCUMBER) { Your("load is too heavy to balance yourself for a kick."); no_kick = TRUE; } else if (u.uinwater && !rn2(2)) { Your("slow motion kick doesn't hit anything."); no_kick = TRUE; } else if (u.utrap) { switch (u.utraptype) { case TT_PIT: pline("There's not enough room to kick down here."); break; case TT_WEB: case TT_BEARTRAP: You_cant("move your %s!", body_part(LEG)); break; default: break; } no_kick = TRUE; } if (no_kick) { /* ignore direction typed before player notices kick failed */ display_nhwindow(WIN_MESSAGE, TRUE); /* --More-- */ return 0; } if(!getdir((char *)0)) return(0); if(!u.dx && !u.dy) return(0); x = u.ux + u.dx; y = u.uy + u.dy; /* KMH -- Kicking boots always succeed */ if (uarmf && uarmf->otyp == KICKING_BOOTS) avrg_attrib = 99; else avrg_attrib = (ACURRSTR+ACURR(A_DEX)+ACURR(A_CON))/3; if(u.uswallow) { switch(rn2(3)) { case 0: You_cant("move your %s!", body_part(LEG)); break; case 1: if (is_animal(u.ustuck->data)) { pline("%s burps loudly.", Monnam(u.ustuck)); break; } default: Your("feeble kick has no effect."); break; } return(1); } if (Levitation) { int xx, yy; xx = u.ux - u.dx; yy = u.uy - u.dy; /* doors can be opened while levitating, so they must be * reachable for bracing purposes * Possible extension: allow bracing against stuff on the side? */ if (isok(xx,yy) && !IS_ROCK(levl[xx][yy].typ) && !IS_DOOR(levl[xx][yy].typ) && (!Is_airlevel(&u.uz) || !OBJ_AT(xx,yy))) { You("have nothing to brace yourself against."); return(0); } } wake_nearby(); u_wipe_engr(2); maploc = &levl[x][y]; /* The next five tests should stay in */ /* their present order: monsters, pools, */ /* objects, non-doors, doors. */ if(MON_AT(x, y)) { struct permonst *mdat; mtmp = m_at(x, y); mdat = mtmp->data; if (!mtmp->mpeaceful || !canspotmon(mtmp)) flags.forcefight = TRUE; /* attack even if invisible */ kick_monster(x, y); flags.forcefight = FALSE; /* see comment in attack_checks() */ if (!canspotmon(mtmp) && /* check x and y; a monster that evades your kick by jumping to an unseen square doesn't leave an I behind */ mtmp->mx == x && mtmp->my == y && !glyph_is_invisible(levl[x][y].glyph) && !(u.uswallow && mtmp == u.ustuck)) map_invisible(x, y); if((Is_airlevel(&u.uz) || Levitation) && flags.move) { int range; range = ((int)youmonst.data->cwt + (weight_cap() + inv_weight())); if (range < 1) range = 1; /* divide by zero avoidance */ range = (3*(int)mdat->cwt) / range; if(range < 1) range = 1; hurtle(-u.dx, -u.dy, range, TRUE); } return(1); } if (glyph_is_invisible(levl[x][y].glyph)) { unmap_object(x, y); newsym(x, y); } if (is_pool(x, y) ^ !!u.uinwater) { /* objects normally can't be removed from water by kicking */ You("splash some water around."); return 1; } kickobj = (struct obj *)0; if (OBJ_AT(x, y) && (!Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) || sobj_at(BOULDER,x,y))) { if(kick_object(x, y)) { if(Is_airlevel(&u.uz)) hurtle(-u.dx, -u.dy, 1, TRUE); /* assume it's light */ return(1); } goto ouch; } if(!IS_DOOR(maploc->typ)) { if(maploc->typ == SDOOR) { if(!Levitation && rn2(30) < avrg_attrib) { cvt_sdoor_to_door(maploc); /* ->typ = DOOR */ pline("Crash! %s a secret door!", /* don't "kick open" when it's locked unless it also happens to be trapped */ (maploc->doormask & (D_LOCKED|D_TRAPPED)) == D_LOCKED ? "Your kick uncovers" : "You kick open"); exercise(A_DEX, TRUE); if(maploc->doormask & D_TRAPPED) { maploc->doormask = D_NODOOR; b_trapped("door", FOOT); } else if (maploc->doormask != D_NODOOR && !(maploc->doormask & D_LOCKED)) maploc->doormask = D_ISOPEN; if (Blind) feel_location(x,y); /* we know it's gone */ else newsym(x,y); if (maploc->doormask == D_ISOPEN || maploc->doormask == D_NODOOR) unblock_point(x,y); /* vision */ return(1); } else goto ouch; } if(maploc->typ == SCORR) { if(!Levitation && rn2(30) < avrg_attrib) { pline("Crash! You kick open a secret passage!"); exercise(A_DEX, TRUE); maploc->typ = CORR; if (Blind) feel_location(x,y); /* we know it's gone */ else newsym(x,y); unblock_point(x,y); /* vision */ return(1); } else goto ouch; } if(IS_THRONE(maploc->typ)) { register int i; if(Levitation) goto dumb; if((Luck < 0 || maploc->doormask) && !rn2(3)) { maploc->typ = ROOM; maploc->doormask = 0; /* don't leave loose ends.. */ (void) mkgold((long)rnd(200), x, y); if (Blind) pline("CRASH! You destroy it."); else { pline("CRASH! You destroy the throne."); newsym(x, y); } exercise(A_DEX, TRUE); return(1); } else if(Luck > 0 && !rn2(3) && !maploc->looted) { (void) mkgold((long) rn1(201, 300), x, y); i = Luck + 1; if(i > 6) i = 6; while(i--) (void) mkobj_at(GEM_CLASS, x, y, TRUE); if (Blind) You("kick %s loose!", something); else { You("kick loose some ornamental coins and gems!"); newsym(x, y); } /* prevent endless milking */ maploc->looted = T_LOOTED; return(1); } else if (!rn2(4)) { if(dunlev(&u.uz) < dunlevs_in_dungeon(&u.uz)) { fall_through(FALSE); return(1); } else goto ouch; } goto ouch; } if(IS_ALTAR(maploc->typ)) { if(Levitation) goto dumb; You("kick %s.",(Blind ? something : "the altar")); if(!rn2(3)) goto ouch; altar_wrath(x, y); exercise(A_DEX, TRUE); return(1); } if(IS_FOUNTAIN(maploc->typ)) { if(Levitation) goto dumb; You("kick %s.",(Blind ? something : "the fountain")); if(!rn2(3)) goto ouch; /* make metal boots rust */ if(uarmf && rn2(3)) if (!rust_dmg(uarmf, "metal boots", 1, FALSE, &youmonst)) { Your("boots get wet."); /* could cause short-lived fumbling here */ } exercise(A_DEX, TRUE); return(1); } if(IS_GRAVE(maploc->typ)) goto ouch; if(IS_TREE(maploc->typ)) { struct obj *treefruit; if (rn2(8)) goto ouch; /* fruit or trouble ? */ if (!rn2(2) && !(maploc->looted & TREE_LOOTED) && (treefruit = rnd_treefruit_at(x, y))) { treefruit->quan = (long)(8 - rnl(8)); if (treefruit->quan > 1L) pline("Some %s fall from the tree!", xname(treefruit)); else pline("%s falls from the tree!", An(xname(treefruit))); scatter(x,y,2,MAY_HIT,treefruit); exercise(A_DEX, TRUE); exercise(A_WIS, TRUE); /* discovered a new food source! */ newsym(x, y); maploc->looted |= TREE_LOOTED; return(1); } else if (!rn2(15) && !(maploc->looted & TREE_SWARM)){ int cnt = rnl(5); coord mm; mm.x = x; mm.y = y; pline("You've disturbed the occupants!"); while (cnt--) if (enexto(&mm, mm.x, mm.y, &mons[PM_KILLER_BEE])) (void) makemon(&mons[PM_KILLER_BEE], mm.x, mm.y, MM_ANGRY); maploc->looted |= TREE_SWARM; return(1); } goto ouch; } #ifdef SINKS if(IS_SINK(maploc->typ)) { int gend = poly_gender(); short washerndx = (gend == 1 || (gend == 2 && rn2(2))) ? PM_INCUBUS : PM_SUCCUBUS; if(Levitation) goto dumb; if(rn2(5)) { if(flags.soundok) pline("Klunk! The pipes vibrate noisily."); else pline("Klunk!"); exercise(A_DEX, TRUE); return(1); } else if(!(maploc->looted & S_LPUDDING) && !rn2(3) && !(mvitals[PM_BLACK_PUDDING].mvflags & G_GONE)) { if (Blind) You_hear("a gushing sound."); else pline("A %s ooze gushes up from the drain!", hcolor(Black)); (void) makemon(&mons[PM_BLACK_PUDDING], x, y, NO_MM_FLAGS); exercise(A_DEX, TRUE); newsym(x,y); maploc->looted |= S_LPUDDING; return(1); } else if(!(maploc->looted & S_LDWASHER) && !rn2(3) && !(mvitals[washerndx].mvflags & G_GONE)) { /* can't resist... */ pline("%s returns!", (Blind ? Something : "The dish washer")); if (makemon(&mons[washerndx], x, y, NO_MM_FLAGS)) newsym(x,y); maploc->looted |= S_LDWASHER; exercise(A_DEX, TRUE); return(1); } else if(!rn2(3)) { pline("Flupp! %s.", (Blind ? "You hear a sloshing sound" : "Muddy waste pops up from the drain")); if(!(maploc->looted & S_LRING)) { /* once per sink */ if (!Blind) You("see a ring shining in its midst."); (void) mkobj_at(RING_CLASS, x, y, TRUE); newsym(x, y); exercise(A_DEX, TRUE); exercise(A_WIS, TRUE); /* a discovery! */ maploc->looted |= S_LRING; } return(1); } goto ouch; } #endif if (maploc->typ == STAIRS || maploc->typ == LADDER || IS_STWALL(maploc->typ)) { if(!IS_STWALL(maploc->typ) && maploc->ladder == LA_DOWN) goto dumb; ouch: pline("Ouch! That hurts!"); exercise(A_DEX, FALSE); exercise(A_STR, FALSE); if (Blind) feel_location(x,y); /* we know we hit it */ if(!rn2(3)) set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); losehp(rnd(ACURR(A_CON) > 15 ? 3 : 5), kickstr(buf), KILLED_BY); if(Is_airlevel(&u.uz) || Levitation) hurtle(-u.dx, -u.dy, rn1(2,4), TRUE); /* assume it's heavy */ return(1); } if (is_drawbridge_wall(x,y) >= 0) { pline_The("drawbridge is unaffected."); if(Levitation) hurtle(-u.dx, -u.dy, rn1(2,4), TRUE); /* it's heavy */ return(1); } goto dumb; } if(maploc->doormask == D_ISOPEN || maploc->doormask == D_BROKEN || maploc->doormask == D_NODOOR) { dumb: exercise(A_DEX, FALSE); if (martial() || ACURR(A_DEX) >= 16 || rn2(3)) { You("kick at empty space."); if (Blind) feel_location(x,y); } else { pline("Dumb move! You strain a muscle."); exercise(A_STR, FALSE); set_wounded_legs(RIGHT_SIDE, 5 + rnd(5)); } if ((Is_airlevel(&u.uz) || Levitation) && rn2(2)) { hurtle(-u.dx, -u.dy, 1, TRUE); return 1; /* you moved, so use up a turn */ } return(0); } /* not enough leverage to kick open doors while levitating */ if(Levitation) goto ouch; exercise(A_DEX, TRUE); /* door is known to be CLOSED or LOCKED */ if(rnl(35) < avrg_attrib + (!martial() ? 0 : ACURR(A_DEX))) { boolean shopdoor = *in_rooms(x, y, SHOPBASE) ? TRUE : FALSE; /* break the door */ if(maploc->doormask & D_TRAPPED) { if (flags.verbose) You("kick the door."); exercise(A_STR, FALSE); maploc->doormask = D_NODOOR; b_trapped("door", FOOT); } else if(ACURR(A_STR) > 18 && !rn2(5) && !shopdoor) { pline("As you kick the door, it shatters to pieces!"); exercise(A_STR, TRUE); maploc->doormask = D_NODOOR; } else { pline("As you kick the door, it crashes open!"); exercise(A_STR, TRUE); maploc->doormask = D_BROKEN; } if (Blind) feel_location(x,y); /* we know we broke it */ else newsym(x,y); unblock_point(x,y); /* vision */ if (shopdoor) { add_damage(x, y, 400L); pay_for_damage("break"); } if ((slev = Is_special(&u.uz)) && slev->flags.town) for(mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if((mtmp->data == &mons[PM_WATCHMAN] || mtmp->data == &mons[PM_WATCH_CAPTAIN]) && couldsee(mtmp->mx, mtmp->my) && mtmp->mpeaceful) { pline("%s yells:", Amonnam(mtmp)); verbalize("Halt, thief! You're under arrest!"); (void) angry_guards(FALSE); break; } } } else { if (Blind) feel_location(x,y); /* we know we hit it */ exercise(A_STR, TRUE); pline("WHAMMM!!!"); if ((slev = Is_special(&u.uz)) && slev->flags.town) for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if ((mtmp->data == &mons[PM_WATCHMAN] || mtmp->data == &mons[PM_WATCH_CAPTAIN]) && mtmp->mpeaceful && couldsee(mtmp->mx, mtmp->my)) { pline("%s yells:", Amonnam(mtmp)); if(levl[x][y].looted & D_WARNED) { verbalize("Halt, vandal! You're under arrest!"); (void) angry_guards(FALSE); } else { verbalize("Hey, stop damaging that door!"); levl[x][y].looted |= D_WARNED; } break; } } } return(1); } STATIC_OVL void drop_to(cc, loc) coord *cc; schar loc; { /* cover all the MIGR_xxx choices generated by down_gate() */ switch (loc) { case MIGR_RANDOM: /* trap door or hole */ if (Is_stronghold(&u.uz)) { cc->x = valley_level.dnum; cc->y = valley_level.dlevel; break; } else if (In_endgame(&u.uz) || Is_botlevel(&u.uz)) { cc->y = cc->x = 0; break; } /* else fall to the next cases */ case MIGR_STAIRS_UP: case MIGR_LADDER_UP: cc->x = u.uz.dnum; cc->y = u.uz.dlevel + 1; break; case MIGR_SSTAIRS: cc->x = sstairs.tolev.dnum; cc->y = sstairs.tolev.dlevel; break; default: case MIGR_NOWHERE: /* y==0 means "nowhere", in which case x doesn't matter */ cc->y = cc->x = 0; break; } } void impact_drop(missile, x, y, dlev) struct obj *missile; xchar x, y, dlev; { schar toloc; register struct obj *obj, *obj2; register struct monst *shkp; long oct, dct, price, debit, robbed; boolean angry, costly, isrock; coord cc; if(!OBJ_AT(x, y)) return; toloc = down_gate(x, y); drop_to(&cc, toloc); if (!cc.y) return; if (dlev) { /* send objects next to player falling through trap door. * checked in obj_delivery(). */ toloc = MIGR_NEAR_PLAYER; cc.y = dlev; } costly = costly_spot(x, y); price = debit = robbed = 0L; angry = FALSE; shkp = (struct monst *) 0; /* if 'costly', we must keep a record of ESHK(shkp) before * it undergoes changes through the calls to stolen_value. * the angry bit must be reset, if needed, in this fn, since * stolen_value is called under the 'silent' flag to avoid * unsavory pline repetitions. */ if(costly) { if ((shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0) { debit = ESHK(shkp)->debit; robbed = ESHK(shkp)->robbed; angry = !shkp->mpeaceful; } } isrock = (missile && missile->otyp == ROCK); oct = dct = 0L; for(obj = level.objects[x][y]; obj; obj = obj2) { obj2 = obj->nexthere; if(obj == missile) continue; /* number of objects in the pile */ oct += obj->quan; if(obj == uball || obj == uchain) continue; /* boulders can fall too, but rarely & never due to rocks */ if((isrock && obj->otyp == BOULDER) || rn2(obj->otyp == BOULDER ? 30 : 3)) continue; obj_extract_self(obj); if(costly) { price += stolen_value(obj, x, y, (costly_spot(u.ux, u.uy) && index(u.urooms, *in_rooms(x, y, SHOPBASE))), TRUE); /* set obj->no_charge to 0 */ if (Has_contents(obj)) picked_container(obj); /* does the right thing */ if (obj->oclass != GOLD_CLASS) obj->no_charge = 0; } add_to_migration(obj); obj->ox = cc.x; obj->oy = cc.y; obj->owornmask = (long)toloc; /* number of fallen objects */ dct += obj->quan; } if (dct && cansee(x,y)) { /* at least one object fell */ const char *what = (dct == 1L ? "object falls" : "objects fall"); if (missile) pline("From the impact, %sother %s.", dct == oct ? "the " : dct == 1L ? "an" : "", what); else if (oct == dct) pline("%s adjacent %s %s.", dct == 1L ? "The" : "All the", what, gate_str); else pline("%s adjacent %s %s.", dct == 1L ? "One of the" : "Some of the", dct == 1L ? "objects falls" : what, gate_str); } if(costly && shkp && price) { if(ESHK(shkp)->robbed > robbed) { You("removed %ld zorkmids worth of goods!", price); if(cansee(shkp->mx, shkp->my)) { if(ESHK(shkp)->customer[0] == 0) (void) strncpy(ESHK(shkp)->customer, plname, PL_NSIZ); if(angry) pline("%s is infuriated!", Monnam(shkp)); else pline("\"%s, you are a thief!\"", plname); } else You_hear("a scream, \"Thief!\""); hot_pursuit(shkp); (void) angry_guards(FALSE); return; } if(ESHK(shkp)->debit > debit) You("owe %s %ld zorkmids for goods lost.", Monnam(shkp), (ESHK(shkp)->debit - debit)); } } /* NOTE: ship_object assumes otmp was FREED from fobj or invent. * is the point of drop. otmp is _not_ an resident: * otmp is either a kicked, dropped, or thrown object. */ boolean ship_object(otmp, x, y, shop_floor_obj) xchar x, y; struct obj *otmp; boolean shop_floor_obj; { schar toloc; xchar ox, oy; coord cc; struct obj *obj; struct trap *t; boolean nodrop, unpaid, container, impact = FALSE; long n = 0L; if (!otmp) return(FALSE); if ((toloc = down_gate(x, y)) == MIGR_NOWHERE) return(FALSE); drop_to(&cc, toloc); if (!cc.y) return(FALSE); /* objects other than attached iron ball always fall down ladder, but have a chance of staying otherwise */ nodrop = (otmp == uball) || (otmp == uchain) || (toloc != MIGR_LADDER_UP && rn2(3)); container = Has_contents(otmp); unpaid = (otmp->unpaid || (container && count_unpaid(otmp->cobj))); if(OBJ_AT(x, y)) { for(obj = level.objects[x][y]; obj; obj = obj->nexthere) if(obj != otmp) n += obj->quan; if(n) impact = TRUE; } /* boulders never fall through trap doors, but they might knock other things down before plugging the hole */ if (otmp->otyp == BOULDER && ((t = t_at(x, y)) != 0) && (t->ttyp == TRAPDOOR || t->ttyp == HOLE)) { if (impact) impact_drop(otmp, x, y, 0); return FALSE; /* let caller finish the drop */ } if (cansee(x, y)) otransit_msg(otmp, nodrop, n); if (nodrop) { if (impact) impact_drop(otmp, x, y, 0); return(FALSE); } if(unpaid || shop_floor_obj) { if(unpaid) { subfrombill(otmp, shop_keeper(*u.ushops)); (void)stolen_value(otmp, u.ux, u.uy, TRUE, FALSE); } else { ox = otmp->ox; oy = otmp->oy; (void)stolen_value(otmp, ox, oy, (costly_spot(u.ux, u.uy) && index(u.urooms, *in_rooms(ox, oy, SHOPBASE))), FALSE); } /* set otmp->no_charge to 0 */ if(container) picked_container(otmp); /* happens to do the right thing */ if(otmp->oclass != GOLD_CLASS) otmp->no_charge = 0; } add_to_migration(otmp); otmp->ox = cc.x; otmp->oy = cc.y; otmp->owornmask = (long)toloc; if(impact) { /* the objs impacted may be in a shop other than * the one in which the hero is located. another * check for a shk is made in impact_drop. it is, e.g., * possible to kick/throw an object belonging to one * shop into another shop through a gap in the wall, * and cause objects belonging to the other shop to * fall down a trap door--thereby getting two shopkeepers * angry at the hero in one shot. */ impact_drop(otmp, x, y, 0); newsym(x,y); } return(TRUE); } void obj_delivery() { register struct obj *otmp, *otmp2; register int nx, ny; long where; for (otmp = migrating_objs; otmp; otmp = otmp2) { otmp2 = otmp->nobj; if (otmp->ox != u.uz.dnum || otmp->oy != u.uz.dlevel) continue; obj_extract_self(otmp); where = otmp->owornmask; /* destination code */ otmp->owornmask = 0L; switch ((int)where) { case MIGR_STAIRS_UP: nx = xupstair, ny = yupstair; break; case MIGR_LADDER_UP: nx = xupladder, ny = yupladder; break; case MIGR_SSTAIRS: nx = sstairs.sx, ny = sstairs.sy; break; case MIGR_NEAR_PLAYER: nx = u.ux, ny = u.uy; break; default: case MIGR_RANDOM: nx = ny = 0; break; } if (nx > 0) { place_object(otmp, nx, ny); stackobj(otmp); scatter(nx, ny, rnd(2), 0, otmp); } else { /* random location */ /* set dummy coordinates because there's no current position for rloco() to update */ otmp->ox = otmp->oy = 0; rloco(otmp); } } } STATIC_OVL void otransit_msg(otmp, nodrop, num) register struct obj *otmp; register boolean nodrop; long num; { char obuf[BUFSZ]; Sprintf(obuf, "%s%s", (otmp->otyp == CORPSE && type_is_pname(&mons[otmp->corpsenm])) ? "" : "The ", xname(otmp)); if(num) { /* means: other objects are impacted */ Sprintf(eos(obuf), " hit%s %s object%s", otmp->quan == 1L ? "s" : "", num == 1L ? "another" : "other", num > 1L ? "s" : ""); if(nodrop) Sprintf(eos(obuf), "."); else Sprintf(eos(obuf), " and fall%s %s.", otmp->quan == 1L ? "s" : "", gate_str); pline("%s", obuf); } else if(!nodrop) pline("%s fall%s %s.", obuf, otmp->quan == 1L ? "s" : "", gate_str); } /* migration destination for objects which fall down to next level */ schar down_gate(x, y) xchar x, y; { struct trap *ttmp; gate_str = 0; /* this matches the player restriction in goto_level() */ if (on_level(&u.uz, &qstart_level) && !ok_to_quest()) return MIGR_NOWHERE; if ((xdnstair == x && ydnstair == y) || (sstairs.sx == x && sstairs.sy == y && !sstairs.up)) { gate_str = "down the stairs"; return (xdnstair == x && ydnstair == y) ? MIGR_STAIRS_UP : MIGR_SSTAIRS; } if (xdnladder == x && ydnladder == y) { gate_str = "down the ladder"; return MIGR_LADDER_UP; } if (((ttmp = t_at(x, y)) != 0 && ttmp->tseen) && (ttmp->ttyp == TRAPDOOR || ttmp->ttyp == HOLE)) { gate_str = (ttmp->ttyp == TRAPDOOR) ? "through the trap door" : "through the hole"; return MIGR_RANDOM; } return MIGR_NOWHERE; } /*dokick.c*/