From: PatR Date: Wed, 2 Feb 2022 13:26:03 +0000 (-0800) Subject: fix #K3455 - rocks vs xorns X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9aea7b587ccd6ec850c0275e0df78eddaa7203ce;p=nethack fix #K3455 - rocks vs xorns Implement the suggestion that falling rock traps and rolling boulder traps be harmless to xorns. I've extended that to all missiles made of stone (rocks, gems, boulders, a handful of other things that will only matter if poly'd hero throws in '<' direction or is hit by stuff scattered by an explosion). I excluded ghosts because they would become even harder to kill and the missile handling would need extra checks to test for blessed objs. --- diff --git a/include/mondata.h b/include/mondata.h index a50183f3e..e7b84721d 100644 --- a/include/mondata.h +++ b/include/mondata.h @@ -220,6 +220,11 @@ #define touch_petrifies(ptr) \ ((ptr) == &mons[PM_COCKATRICE] || (ptr) == &mons[PM_CHICKATRICE]) +/* missiles made of rocks don't harm these: xorns and earth elementals + (but not ghosts and shades because that would impact all missile use + and also require an exception for blessed rocks/gems/boulders) */ +#define passes_rocks(ptr) (passes_walls(ptr) && !unsolid(ptr)) + #define is_mind_flayer(ptr) \ ((ptr) == &mons[PM_MIND_FLAYER] || (ptr) == &mons[PM_MASTER_MIND_FLAYER]) diff --git a/include/obj.h b/include/obj.h index 685c9165f..6ef303f48 100644 --- a/include/obj.h +++ b/include/obj.h @@ -226,6 +226,12 @@ struct obj { #define uslinging() (uwep && objects[uwep->otyp].oc_skill == P_SLING) /* 'is_quest_artifact()' only applies to the current role's artifact */ #define any_quest_artifact(o) ((o)->oartifact >= ART_ORB_OF_DETECTION) +/* 'missile' aspect is up to the caller and does not imply is_missile(); + rings might be launched as missiles when being scattered by an explosion */ +#define stone_missile(o) \ + ((o) && (objects[(o)->otyp].oc_material == GEMSTONE \ + || (objects[(o)->otyp].oc_material == MINERAL)) \ + && (o)->oclass != RING_CLASS) /* Armor */ #define is_shield(otmp) \ diff --git a/src/do_name.c b/src/do_name.c index cde27e443..c2103b289 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -1728,6 +1728,9 @@ x_monnam( falseCap = (*pm_name != lowc(*pm_name)); char *bp; + if (mtmp == &g.youmonst) + return strcpy(buf, "you"); /* ignore article, "invisible", &c */ + if (g.program_state.gameover) suppress |= SUPPRESS_HALLUCINATION; if (article == ARTICLE_YOUR && !mtmp->mtame) diff --git a/src/dothrow.c b/src/dothrow.c index bb89aaf90..c9d07789b 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -535,9 +535,9 @@ endmultishot(boolean verbose) /* Object hits floor at hero's feet. Called from drop(), throwit(), hold_another_object(), litter(). */ void -hitfloor(struct obj *obj, - boolean verbosely) /* usually True; False if caller has given - drop message */ +hitfloor( + struct obj *obj, + boolean verbosely) /* usually True; False if caller has given drop mesg */ { if (IS_SOFT(levl[u.ux][u.uy].typ) || u.uinwater || u.uswallow) { dropy(obj); @@ -1056,7 +1056,8 @@ check_shop_obj(struct obj *obj, xchar x, xchar y, boolean broken) } /* Will 'obj' cause damage if it falls on hero's head when thrown upward? - Not used to handle things which break when they hit. */ + Not used to handle things which break when they hit. + Stone missile hitting hero w/ Passes_walls attribute handled separately. */ static boolean harmless_missile(struct obj *obj) { @@ -1167,13 +1168,16 @@ toss_up(struct obj *obj, boolean hitsroof) hitfloor(obj, FALSE); g.thrownobj = 0; } else { /* neither potion nor other breaking object */ - boolean is_silver = (objects[otyp].oc_material == SILVER), + int material = objects[otyp].oc_material; + boolean is_silver = (material == SILVER), less_damage = (uarmh && is_metallic(uarmh) && (!is_silver || !Hate_silver)), + harmless = (stone_missile(obj) + && passes_rocks(g.youmonst.data)), artimsg = FALSE; int dmg = dmgval(obj, &g.youmonst); - if (obj->oartifact) + if (obj->oartifact && !harmless) /* need a fake die roll here; rn1(18,2) avoids 1 and 20 */ artimsg = artifact_hit((struct monst *) 0, &g.youmonst, obj, &dmg, rn1(18, 2)); @@ -1205,15 +1209,24 @@ toss_up(struct obj *obj, boolean hitsroof) dmg = Maybe_Half_Phys(dmg); if (uarmh) { - if (less_damage && dmg < (Upolyd ? u.mh : u.uhp)) { - if (!artimsg) - pline("Fortunately, you are wearing a hard helmet."); + /* note: 'harmless' and 'petrifier' are mutually exclusive */ + if ((less_damage && dmg < (Upolyd ? u.mh : u.uhp)) || harmless) { + if (!artimsg) { + if (!harmless) /* !harmless => less_damage here */ + pline("Fortunately, you are wearing a hard helmet."); + else + pline("Unfortunately, you are wearing %s.", + an(helm_simple_name(uarmh))); /* helm or hat */ + } /* helmet definitely protects you when it blocks petrification */ } else if (!petrifier) { if (flags.verbose) Your("%s does not protect you.", helm_simple_name(uarmh)); } + /* stone missile against hero in xorn form would have been + harmless, but hitting a worn helmet negates that */ + harmless = FALSE; } else if (petrifier && !Stone_resistance && !(poly_when_stoned(g.youmonst.data) && polymon(PM_STONE_GOLEM))) { @@ -1229,9 +1242,13 @@ toss_up(struct obj *obj, boolean hitsroof) } if (is_silver && Hate_silver) pline_The("silver sears you!"); + if (harmless) + hit(thesimpleoname(obj), &g.youmonst, " but doesn't hurt."); + hitfloor(obj, TRUE); g.thrownobj = 0; - losehp(dmg, "falling object", KILLED_BY_AN); + if (!harmless) + losehp(dmg, "falling object", KILLED_BY_AN); } return TRUE; } @@ -1692,8 +1709,9 @@ tmiss(struct obj *obj, struct monst *mon, boolean maybe_wakeup) * Also used for kicked objects and for polearms/grapnel applied at range. */ int -thitmonst(register struct monst *mon, - register struct obj *obj) /* g.thrownobj or g.kickedobj or uwep */ +thitmonst( + struct monst *mon, + struct obj *obj) /* g.thrownobj or g.kickedobj or uwep */ { register int tmp; /* Base chance to hit */ register int disttmp; /* distance modifier */ diff --git a/src/mthrowu.c b/src/mthrowu.c index 82838183f..cf775ec7d 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -37,17 +37,18 @@ m_has_launcher_and_ammo(struct monst* mtmp) return FALSE; } -/* hero is hit by something other than a monster */ +/* hero is hit by something other than a monster (though it could be a + missile thrown or shot by a monster) */ int thitu( - int tlev, + int tlev, /* pseudo-level used when deciding whether to hit hero's AC */ int dam, struct obj **objp, const char *name) /* if null, then format `*objp' */ { struct obj *obj = objp ? *objp : 0; const char *onm, *knm; - boolean is_acid; + boolean is_acid, named = (name != 0); int kprefix = KILLED_BY_AN, dieroll; char onmbuf[BUFSZ], knmbuf[BUFSZ]; @@ -90,6 +91,13 @@ thitu( if (is_acid && Acid_resistance) { pline("It doesn't seem to hurt you."); monstseesu(M_SEEN_ACID); + } else if (stone_missile(obj) && passes_rocks(g.youmonst.data)) { + /* use 'named' as an approximation for "hitting from above"; + we avoid "passes through you" for horizontal flight path + because missile stops and that wording would suggest that + it should keep going */ + pline("It %s you.", + named ? "passes harmlessly through" : "doesn't harm"); } else if (obj && obj->oclass == POTION_CLASS) { /* an explosion which scatters objects might hit hero with one (potions deliberately thrown at hero are handled by m_throw) */ @@ -280,8 +288,8 @@ ohitmon( struct monst *mtmp, /* accidental target, located at */ struct obj *otmp, /* missile; might be destroyed by drop_throw */ int range, /* how much farther will object travel if it misses; - use -1 to signify to keep going even after hit, - unless it's gone (used for rolling_boulder_traps) */ + * use -1 to signify to keep going even after hit, + * unless it's gone (used for rolling_boulder_traps) */ boolean verbose)/* give message(s) even when you can't see what happened */ { int damage, tmp; @@ -325,6 +333,9 @@ ohitmon( potionhit(mtmp, otmp, POTHIT_OTHER_THROW); return 1; } else { + int material = objects[otmp->otyp].oc_material; + boolean harmless = (stone_missile(otmp) && passes_rocks(mtmp->data)); + damage = dmgval(otmp, mtmp); if (otmp->otyp == ACID_VENOM && resists_acid(mtmp)) damage = 0; @@ -336,12 +347,20 @@ ohitmon( seemimic(mtmp); mtmp->msleeping = 0; if (vis) { - if (otmp->otyp == EGG) + if (otmp->otyp == EGG) { pline("Splat! %s is hit with %s egg!", Monnam(mtmp), otmp->known ? an(mons[otmp->corpsenm].pmnames[NEUTRAL]) : "an"); - else - hit(distant_name(otmp, mshot_xname), mtmp, exclam(damage)); + } else { + char how[BUFSZ]; + + if (!harmless) + Strcpy(how, exclam(damage)); /* "!" or "." */ + else + Sprintf(how, " but passes harmlessly through %.9s.", + mhim(mtmp)); + hit(distant_name(otmp, mshot_xname), mtmp, how); + } } else if (verbose && !g.mtarget) pline("%s%s is hit%s", (otmp->otyp == EGG) ? "Splat! " : "", Monnam(mtmp), exclam(damage)); @@ -361,8 +380,7 @@ ohitmon( } } } - if (objects[otmp->otyp].oc_material == SILVER - && mon_hates_silver(mtmp)) { + if (material == SILVER && mon_hates_silver(mtmp)) { boolean flesh = (!noncorporeal(mtmp->data) && !amorphous(mtmp->data)); @@ -395,7 +413,8 @@ ohitmon( damage = 0; } - if (!DEADMONSTER(mtmp)) { /* might already be dead (if petrified) */ + /* might already be dead (if petrified) */ + if (!harmless && !DEADMONSTER(mtmp)) { mtmp->mhp -= damage; if (DEADMONSTER(mtmp)) { if (vis || (verbose && !g.mtarget)) diff --git a/src/trap.c b/src/trap.c index 5ff0168d4..e342c5e14 100644 --- a/src/trap.c +++ b/src/trap.c @@ -9,8 +9,7 @@ extern const char *const destroy_strings[][3]; /* from zap.c */ static boolean keep_saddle_with_steedcorpse(unsigned, struct obj *, struct obj *); -static boolean mu_maybe_destroy_web(struct monst *, boolean, - struct trap *); +static boolean mu_maybe_destroy_web(struct monst *, boolean, struct trap *); static struct obj *t_missile(int, struct trap *); static int trapeffect_arrow_trap(struct monst *, struct trap *, unsigned); static int trapeffect_dart_trap(struct monst *, struct trap *, unsigned); @@ -1060,6 +1059,7 @@ trapeffect_rocktrap( unsigned trflags UNUSED) { struct obj *otmp; + boolean harmless = FALSE; if (mtmp == &g.youmonst) { if (trap->once && trap->tseen && !rn2(15)) { @@ -1078,20 +1078,31 @@ trapeffect_rocktrap( pline("A trap door in %s opens and %s falls on your %s!", the(ceiling(u.ux, u.uy)), an(xname(otmp)), body_part(HEAD)); if (uarmh) { - if (is_metallic(uarmh)) { + /* normally passes_rocks() would protect againt a falling + rock, but not when wearing a helmet */ + if (passes_rocks(g.youmonst.data)) { + pline("Unfortunately, you are wearing %s.", + an(helm_simple_name(uarmh))); /* helm or hat */ + dmg = 2; + } else if (is_metallic(uarmh)) { pline("Fortunately, you are wearing a hard helmet."); dmg = 2; } else if (flags.verbose) { pline("%s does not protect you.", Yname2(uarmh)); } + } else if (passes_rocks(g.youmonst.data)) { + pline("It passes harmlessly through you."); + harmless = TRUE; } if (!Blind) otmp->dknown = 1; stackobj(otmp); newsym(u.ux, u.uy); /* map the rock */ - losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); - exercise(A_STR, FALSE); + if (!harmless) { + losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN); + exercise(A_STR, FALSE); + } } } else { boolean in_sight = canseemon(mtmp) || (mtmp == u.usteed); @@ -2655,15 +2666,13 @@ force_launch_placement(void) int launch_obj( short otyp, - register int x1, - register int y1, - register int x2, - register int y2, + int x1, int y1, + int x2, int y2, int style) { - register struct monst *mtmp; - register struct obj *otmp, *otmp2; - register int dx, dy; + struct monst *mtmp; + struct obj *otmp, *otmp2; + int dx, dy; struct obj *singleobj; boolean used_up = FALSE; boolean otherside = FALSE; @@ -2733,8 +2742,8 @@ launch_obj( singleobj->otrapped = 1; style &= ~LAUNCH_KNOWN; /*FALLTHRU*/ - roll: case ROLL: + roll: delaycnt = 2; /*FALLTHRU*/ default: @@ -5637,14 +5646,13 @@ b_trapped(const char* item, int bodypart) } /* Monster is hit by trap. */ -/* Note: doesn't work if both obj and d_override are null */ static boolean thitm( - int tlev, - struct monst *mon, - struct obj *obj, - int d_override, - boolean nocorpse) + int tlev, /* missile's attack level */ + struct monst *mon, /* target */ + struct obj *obj, /* missile; might be Null */ + int d_override, /* non-zero: force hit for this amount of damage */ + boolean nocorpse) /* True: a trap is completely burning up the target */ { int strike; boolean trapkilled = FALSE; @@ -5664,9 +5672,11 @@ thitm( pline("%s is almost hit by %s!", Monnam(mon), doname(obj)); } else { int dam = 1; + boolean harmless = (stone_missile(obj) && passes_rocks(mon->data)); if (obj && cansee(mon->mx, mon->my)) - pline("%s is hit by %s!", Monnam(mon), doname(obj)); + pline("%s is hit by %s%s", Monnam(mon), doname(obj), + harmless ? " but is not harmed." : "!"); if (d_override) { dam = d_override; } else if (obj) { @@ -5674,15 +5684,19 @@ thitm( if (dam < 1) dam = 1; } - mon->mhp -= dam; - if (mon->mhp <= 0) { - int xx = mon->mx, yy = mon->my; + if (!harmless) { + mon->mhp -= dam; + if (mon->mhp <= 0) { + int xx = mon->mx, yy = mon->my; - monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS); - if (DEADMONSTER(mon)) { - newsym(xx, yy); - trapkilled = TRUE; + monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS); + if (DEADMONSTER(mon)) { + newsym(xx, yy); + trapkilled = TRUE; + } } + } else { + strike = 0; /* harmless; don't use up the missile */ } } if (obj && (!strike || d_override)) { diff --git a/src/uhitm.c b/src/uhitm.c index 103a274af..bf03a6dfc 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -295,11 +295,12 @@ mon_maybe_wakeup_on_hit(struct monst *mtmp) using attack type aatyp and/or weapon. larger value == easier to hit */ int -find_roll_to_hit(struct monst *mtmp, - uchar aatyp, /* usually AT_WEAP or AT_KICK */ - struct obj *weapon, /* uwep or uswapwep or NULL */ - int *attk_count, - int *role_roll_penalty) +find_roll_to_hit( + struct monst *mtmp, + uchar aatyp, /* usually AT_WEAP or AT_KICK */ + struct obj *weapon, /* uwep or uswapwep or NULL */ + int *attk_count, + int *role_roll_penalty) { int tmp, tmp2; @@ -574,10 +575,9 @@ known_hitum( /* hit the monster next to you and the monsters to the left and right of it; return False if the primary target is killed, True otherwise */ static boolean -hitum_cleave(struct monst *target, /* non-Null; forcefight at nothing doesn't - cleave... */ - struct attack *uattk) /* ... but we don't enforce that here; Null - works ok */ +hitum_cleave( + struct monst *target, /* non-Null; forcefight at nothing doesn't cleave +*/ + struct attack *uattk) /*+ but we don't enforce that here; Null works ok */ { /* swings will be delivered in alternate directions; with consecutive attacks it will simulate normal swing and backswing; when swings @@ -723,12 +723,13 @@ hmon(struct monst *mon, DISABLE_WARNING_FORMAT_NONLITERAL -/* guts of hmon() */ +/* guts of hmon(); returns True if 'mon' survives */ static boolean -hmon_hitmon(struct monst *mon, - struct obj *obj, - int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */ - int dieroll) +hmon_hitmon( + struct monst *mon, + struct obj *obj, + int thrown, /* HMON_xxx (0 => hand-to-hand, other => ranged) */ + int dieroll) { int tmp; struct permonst *mdat = mon->data; @@ -751,7 +752,7 @@ hmon_hitmon(struct monst *mon, /* not grapnels; applied implies uwep */ || (thrown == HMON_APPLIED && is_pole(uwep))); int jousting = 0; - long silverhit = 0L; + int material = obj ? objects[obj->otyp].oc_material : 0; int wtype; struct obj *monwep; char saved_oname[BUFSZ]; @@ -760,6 +761,8 @@ hmon_hitmon(struct monst *mon, wakeup(mon, TRUE); if (!obj) { /* attack with bare hands */ + long silverhit = 0L; /* armor mask */ + if (mdat == &mons[PM_SHADE]) { tmp = 0; } else { @@ -781,10 +784,20 @@ hmon_hitmon(struct monst *mon, silvermsg = TRUE; } else { + /* stone missile does not hurt xorn or earth elemental, but doesn't + pass all the way through and continue on to some further target */ + if ((thrown == HMON_THROWN || thrown == HMON_KICKED) /* not Applied */ + && stone_missile(obj) && passes_rocks(mdat)) { + hit(mshot_xname(obj), mon, " but does no harm."); + return TRUE; + } + /* remember obj's name since it might end up being destroyed and + we'll want to use it after that */ if (!(artifact_light(obj) && obj->lamplit)) Strcpy(saved_oname, cxname(obj)); else Strcpy(saved_oname, bare_artifactname(obj)); + if (obj->oclass == WEAPON_CLASS || is_weptool(obj) || obj->oclass == GEM_CLASS) { /* is it not a melee weapon? */ @@ -803,10 +816,8 @@ hmon_hitmon(struct monst *mon, tmp = 0; else tmp = rnd(2); - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { - silvermsg = TRUE; - silverobj = TRUE; + if (material == SILVER && mon_hates_silver(mon)) { + silvermsg = silverobj = TRUE; /* if it will already inflict dmg, make it worse */ tmp += rnd((tmp) ? 20 : 10); } @@ -891,10 +902,8 @@ hmon_hitmon(struct monst *mon, return TRUE; hittxt = TRUE; } - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { - silvermsg = TRUE; - silverobj = TRUE; + if (material == SILVER && mon_hates_silver(mon)) { + silvermsg = silverobj = TRUE; } if (artifact_light(obj) && obj->lamplit && mon_hates_light(mon)) @@ -1168,8 +1177,7 @@ hmon_hitmon(struct monst *mon, } /* things like silver wands can arrive here so we need another silver check; blessed check too */ - if (objects[obj->otyp].oc_material == SILVER - && mon_hates_silver(mon)) { + if (material == SILVER && mon_hates_silver(mon)) { tmp += rnd(20); silvermsg = silverobj = TRUE; } @@ -1336,9 +1344,9 @@ hmon_hitmon(struct monst *mon, /* iron weapon using melee or polearm hit [3.6.1: metal weapon too; also allow either or both weapons to cause split when twoweap] */ && obj && (obj == uwep || (u.twoweap && obj == uswapwep)) - && ((objects[obj->otyp].oc_material == IRON + && ((material == IRON /* allow scalpel and tsurugi to split puddings */ - || objects[obj->otyp].oc_material == METAL) + || material == METAL) /* but not bashing with darts, arrows or ya */ && !(is_ammo(obj) || is_missile(obj))) && hand_to_hand) { diff --git a/src/zap.c b/src/zap.c index dcecbe39c..e4497f382 100644 --- a/src/zap.c +++ b/src/zap.c @@ -3309,11 +3309,16 @@ exclam(int force) } void -hit(const char *str, struct monst *mtmp, - const char *force) /* usually either "." or "!" */ +hit(const char *str, /* zap text or missile name */ + struct monst *mtmp, /* target; for missile, might be hero */ + const char *force) /* usually either "." or "!" via exclam() */ { - if ((!cansee(g.bhitpos.x, g.bhitpos.y) && !canspotmon(mtmp) - && !engulfing_u(mtmp)) || !flags.verbose) + boolean verbosely = (mtmp == &g.youmonst + || (flags.verbose + && (cansee(g.bhitpos.x, g.bhitpos.y) + || canspotmon(mtmp) || engulfing_u(mtmp)))); + + if (!verbosely) pline("%s %s it.", The(str), vtense(str, "hit")); else pline("%s %s %s%s", The(str), vtense(str, "hit"),