diff --git a/src/dokick.c b/src/dokick.c new file mode 100644 index 000000000..2ed9ad52b --- /dev/null +++ b/src/dokick.c @@ -0,0 +1,1393 @@ +/* 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 && !mon->mavenge))) { + You_feel("like a caitiff!"); + adjalign(-1); + } + + /* squeeze some guilt feelings... */ + if(mon->mtame) { + abuse_dog(mon); + if (mon->mtame) + monflee(mon, (dmg ? rnd(dmg) : 1), FALSE, FALSE); + else + mon->mflee = 0; + } + + 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 && !mon->mtrapped) { + /* 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) mintrap(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); + if (sum == 2) + break; /* Defender died */ + } 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) ? "swoops" : + (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 { +#ifdef GOLDOBJ + long value = gold->quan * objects[gold->otyp].oc_cost; +#endif + 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)); +#ifndef GOLDOBJ + mtmp->mgold += gold->quan; +#endif + if (mtmp->isshk) { + long robbed = ESHK(mtmp)->robbed; + + if (robbed) { +#ifndef GOLDOBJ + robbed -= gold->quan; +#else + robbed -= value; +#endif + if (robbed < 0) robbed = 0; + pline_The("amount %scovers %s recent losses.", + !robbed ? "" : "partially ", + mhis(mtmp)); + ESHK(mtmp)->robbed = robbed; + if(!robbed) + make_happy_shk(mtmp, FALSE); + } else { + if(mtmp->mpeaceful) { +#ifndef GOLDOBJ + ESHK(mtmp)->credit += gold->quan; +#else + ESHK(mtmp)->credit += value; +#endif + 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) { +#ifndef GOLDOBJ + if (gold->quan > goldreqd + + (u.ugold + u.ulevel*rn2(5))/ACURR(A_CHA)) +#else + if (value > goldreqd + + (money_cnt(invent) + u.ulevel*rn2(5))/ACURR(A_CHA)) +#endif + mtmp->mpeaceful = TRUE; + } + } + if (mtmp->mpeaceful) + verbalize("That should do. Now beat it!"); + else verbalize("That's not enough, coward!"); + } + +#ifndef GOLDOBJ + dealloc_obj(gold); +#else + add_to_minv(mtmp, gold); +#endif + 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_TREE(maploc->typ)) what = "a tree"; + 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"; +#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*/