diff --git a/include/extern.h b/include/extern.h index 59e84ba66..9cbc33bf2 100644 --- a/include/extern.h +++ b/include/extern.h @@ -658,7 +658,7 @@ extern int thitmonst(struct monst *, struct obj *); extern int hero_breaks(struct obj *, coordxy, coordxy, unsigned); extern int breaks(struct obj *, coordxy, coordxy); extern void release_camera_demon(struct obj *, coordxy, coordxy); -extern void breakobj(struct obj *, coordxy, coordxy, boolean, boolean); +extern int breakobj(struct obj *, coordxy, coordxy, boolean, boolean); extern boolean breaktest(struct obj *); extern boolean walk_path(coord *, coord *, boolean(*)(void *, coordxy, coordxy), genericptr_t); diff --git a/include/hack.h b/include/hack.h index aadac06bb..e6fa6d4b7 100644 --- a/include/hack.h +++ b/include/hack.h @@ -303,7 +303,8 @@ enum cost_alteration_types { COST_BRKLCK = 15, /* break box/chest's lock */ COST_RUST = 16, /* rust damage */ COST_ROT = 17, /* rotting attack */ - COST_CORRODE = 18 /* acid damage */ + COST_CORRODE = 18, /* acid damage */ + COST_CRACK = 19, /* damage to crystal armor */ }; /* read.c, create_particular() & create_particular_parse() */ diff --git a/include/obj.h b/include/obj.h index fd5d0330a..f25f6ff13 100644 --- a/include/obj.h +++ b/include/obj.h @@ -436,6 +436,7 @@ struct obj { #define ERODE_RUST 1 #define ERODE_ROT 2 #define ERODE_CORRODE 3 +#define ERODE_CRACK 4 /* crystal armor */ /* erosion flags for erode_obj() */ #define EF_NONE 0 diff --git a/include/objclass.h b/include/objclass.h index bfe5ae66e..309b19d47 100644 --- a/include/objclass.h +++ b/include/objclass.h @@ -192,6 +192,9 @@ extern NEARDATA struct objdescr obj_descr[NUM_OBJECTS + 1]; /* primary damage: fire/rust/--- */ /* is_flammable(otmp), is_rottable(otmp) in mkobj.c */ #define is_rustprone(otmp) (objects[otmp->otyp].oc_material == IRON) +#define is_crackable(otmp) \ + (objects[(otmp)->otyp].oc_material == GLASS \ + && (otmp)->oclass == ARMOR_CLASS) /* erosion_matters() */ /* secondary damage: rot/acid/acid */ #define is_corrodeable(otmp) \ (objects[otmp->otyp].oc_material == COPPER \ @@ -199,6 +202,7 @@ extern NEARDATA struct objdescr obj_descr[NUM_OBJECTS + 1]; /* subject to any damage */ #define is_damageable(otmp) \ (is_rustprone(otmp) || is_flammable(otmp) \ - || is_rottable(otmp) || is_corrodeable(otmp)) + || is_rottable(otmp) || is_corrodeable(otmp) \ + || is_crackable(otmp)) #endif /* OBJCLASS_H */ diff --git a/src/dothrow.c b/src/dothrow.c index 831a1b0a7..83faea149 100644 --- a/src/dothrow.c +++ b/src/dothrow.c @@ -1273,8 +1273,7 @@ toss_up(struct obj *obj, boolean hitsroof) if (breaktest(obj)) { pline("%s hits the %s.", Doname2(obj), ceiling(u.ux, u.uy)); breakmsg(obj, !Blind); - breakobj(obj, u.ux, u.uy, TRUE, TRUE); - return FALSE; + return breakobj(obj, u.ux, u.uy, TRUE, TRUE) ? FALSE : TRUE; } action = "hits"; } else { @@ -1297,8 +1296,9 @@ toss_up(struct obj *obj, boolean hitsroof) ? rnd(25) : 0; breakmsg(obj, !Blind); - breakobj(obj, u.ux, u.uy, TRUE, TRUE); - obj = 0; /* it's now gone */ + if (breakobj(obj, u.ux, u.uy, TRUE, TRUE)) + obj = 0; /* it's now gone */ + switch (otyp) { case EGG: if (petrifier && !Stone_resistance @@ -1326,7 +1326,11 @@ toss_up(struct obj *obj, boolean hitsroof) default: break; } - return FALSE; + if (!obj) + return FALSE; + /* 'obj' still exists, so drop it and return True */ + hitfloor(obj, FALSE); + gt.thrownobj = 0; } else if (harmless_missile(obj)) { pline("It doesn't hurt."); hitfloor(obj, FALSE); @@ -1739,9 +1743,10 @@ throwit(struct obj *obj, nh_delay_output(); tmp_at(DISP_END, 0); breakmsg(obj, cansee(gb.bhitpos.x, gb.bhitpos.y)); - breakobj(obj, gb.bhitpos.x, gb.bhitpos.y, TRUE, TRUE); - clear_thrownobj = TRUE; - goto throwit_return; + if (breakobj(obj, gb.bhitpos.x, gb.bhitpos.y, TRUE, TRUE)) { + clear_thrownobj = TRUE; + goto throwit_return; + } } if (!Deaf && !Underwater) { /* Some sound effects when item lands in water or lava */ @@ -2334,9 +2339,10 @@ gem_accept(register struct monst *mon, register struct obj *obj) * Return 0 if the object didn't break, 1 if the object broke. */ int -hero_breaks(struct obj *obj, - coordxy x, coordxy y, /* object location (ox, oy may not be right) */ - unsigned breakflags) +hero_breaks( + struct obj *obj, + coordxy x, coordxy y, /* object location (ox, oy may not be right) */ + unsigned breakflags) { /* from_invent: thrown or dropped by player; maybe on shop bill; by-hero is implicit so callers don't need to specify BRK_BY_HERO */ @@ -2351,8 +2357,7 @@ hero_breaks(struct obj *obj, return 0; breakmsg(obj, in_view); - breakobj(obj, x, y, TRUE, from_invent); - return 1; + return breakobj(obj, x, y, TRUE, from_invent); } /* @@ -2361,16 +2366,16 @@ hero_breaks(struct obj *obj, * Return 0 if the object doesn't break, 1 if the object broke. */ int -breaks(struct obj *obj, - coordxy x, coordxy y) /* object location (ox, oy may not be right) */ +breaks( + struct obj *obj, + coordxy x, coordxy y) /* object location (ox, oy may not be right) */ { boolean in_view = Blind ? FALSE : cansee(x, y); if (!breaktest(obj)) return 0; breakmsg(obj, in_view); - breakobj(obj, x, y, FALSE, FALSE); - return 1; + return breakobj(obj, x, y, FALSE, FALSE); } void @@ -2390,18 +2395,25 @@ release_camera_demon(struct obj *obj, coordxy x, coordxy y) } /* - * Unconditionally break an object. Assumes all resistance checks + * Break an object. Breakable armor goes through erosion steps; other + * items break unconditionally. Assumes all resistance checks * and break messages have been delivered prior to getting here. + * (No longer true; breakmsg() is silent for crackable armor and we + * call erode_obj() for it and that delivers a damaged-the-item message.) */ -void +int breakobj( struct obj *obj, - coordxy x, coordxy y, /* object location (ox, oy may not be right) */ - boolean hero_caused, /* is this the hero's fault? */ + coordxy x, coordxy y, /* object location (ox, oy may not be right) */ + boolean hero_caused, /* is this the hero's fault? */ boolean from_invent) { boolean fracture = FALSE; + if (is_crackable(obj)) /* if erodeproof, erode_obj() will say so */ + return (erode_obj(obj, armor_simple_name(obj), ERODE_CRACK, + EF_DESTROY | EF_VERBOSE) == ER_DESTROYED); + switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { case MIRROR: if (hero_caused) @@ -2478,6 +2490,7 @@ breakobj( } if (!fracture) delobj(obj); + return 1; } /* @@ -2490,15 +2503,16 @@ breaktest(struct obj *obj) int nonbreakchance = 1; /* chance for non-artifacts to resist */ /* this may need to be changed if actual glass armor gets added someday; - for now, it affects crystal plate mail and helm of brilliance */ - if (obj->oclass == ARMOR_CLASS) - nonbreakchance = 95; + for now, it affects crystal plate mail and helm of brilliance; + either of them will have to be cracked 4 times before breaking */ + if (obj->oclass == ARMOR_CLASS && objects[obj->otyp].oc_material == GLASS) + nonbreakchance = 90; if (obj_resists(obj, nonbreakchance, 99)) - return 0; + return FALSE; if (objects[obj->otyp].oc_material == GLASS && !obj->oartifact && obj->oclass != GEM_CLASS) - return 1; + return TRUE; switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { case EXPENSIVE_CAMERA: case POT_WATER: /* really, all potions */ @@ -2507,9 +2521,9 @@ breaktest(struct obj *obj) case MELON: case ACID_VENOM: case BLINDING_VENOM: - return 1; + return TRUE; default: - return 0; + return FALSE; } } @@ -2518,14 +2532,15 @@ breakmsg(struct obj *obj, boolean in_view) { const char *to_pieces; + if (is_crackable(obj)) /* breakobj() will call erode_obj() for message */ + return; + to_pieces = ""; switch (obj->oclass == POTION_CLASS ? POT_WATER : obj->otyp) { default: /* glass or crystal wand */ if (obj->oclass != WAND_CLASS) impossible("breaking odd object (%d)?", obj->otyp); /*FALLTHRU*/ - case CRYSTAL_PLATE_MAIL: - case HELM_OF_BRILLIANCE: case LENSES: case MIRROR: case CRYSTAL_BALL: diff --git a/src/makemon.c b/src/makemon.c index 3e0490e2a..59eac9516 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -2022,7 +2022,8 @@ mongets(register struct monst *mtmp, int otyp) otmp->cursed = FALSE; if (otmp->spe < 0) otmp->spe = 0; - otmp->oerodeproof = TRUE; + otmp->oerodeproof = 1; + otmp->oeroded = otmp->oeroded2 = 0; } else if (is_mplayer(mtmp->data) && is_sword(otmp)) { otmp->spe = (3 + rn2(4)); } diff --git a/src/mkobj.c b/src/mkobj.c index accd13eb3..b7b3baa45 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -201,7 +201,8 @@ mkobj_erosions(struct obj *otmp) if (!rn2(100)) { otmp->oerodeproof = 1; } else { - if (!rn2(80) && (is_flammable(otmp) || is_rustprone(otmp))) { + if (!rn2(80) && (is_flammable(otmp) || is_rustprone(otmp) + || is_crackable(otmp))) { do { otmp->oeroded++; } while (otmp->oeroded < 3 && !rn2(9)); @@ -729,7 +730,7 @@ bill_dummy_object(struct obj *otmp) static const char *const alteration_verbs[] = { "cancel", "drain", "uncharge", "unbless", "uncurse", "disenchant", "degrade", "dilute", "erase", "burn", "neutralize", "destroy", "splatter", - "bite", "open", "break the lock on", "rust", "rot", "tarnish" + "bite", "open", "break the lock on", "rust", "rot", "tarnish", "crack", }; /* possibly bill for an object which the player has just modified */ diff --git a/src/objnam.c b/src/objnam.c index 5b9212e67..979370ac5 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1039,7 +1039,9 @@ add_erosion_words(struct obj* obj, char* prefix) Strcat(prefix, "thoroughly "); break; } - Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt "); + Strcat(prefix, is_rustprone(obj) ? "rusty " + : is_crackable(obj) ? "cracked " + : "burnt "); } if (obj->oeroded2 && !iscrys) { switch (obj->oeroded2) { @@ -1052,21 +1054,21 @@ add_erosion_words(struct obj* obj, char* prefix) } Strcat(prefix, is_corrodeable(obj) ? "corroded " : "rotted "); } + /* note: it is possible for an item to be both eroded and erodeproof + (cursed scroll of destroy armor read while confused erodeproofs an + item of armor without repairing existing erosion) */ if (rknown && obj->oerodeproof) - Strcat(prefix, iscrys - ? "fixed " - : is_rustprone(obj) - ? "rustproof " - : is_corrodeable(obj) - ? "corrodeproof " /* "stainless"? */ - : is_flammable(obj) - ? "fireproof " - : ""); + Strcat(prefix, iscrys ? "fixed " + : is_rustprone(obj) ? "rustproof " + : is_corrodeable(obj) ? "corrodeproof " + : is_flammable(obj) ? "fireproof " + : is_crackable(obj) ? "tempered " /* hardened */ + : ""); } /* used to prevent rust on items where rust makes no difference */ boolean -erosion_matters(struct obj* obj) +erosion_matters(struct obj *obj) { switch (obj->oclass) { case TOOL_CLASS: @@ -1564,8 +1566,7 @@ not_fully_identified(struct obj* otmp) && otmp->oclass != BALL_CLASS)) /* (useless) */ return FALSE; else /* lack of `rknown' only matters for vulnerable objects */ - return (boolean) (is_rustprone(otmp) || is_corrodeable(otmp) - || is_flammable(otmp)); + return (boolean) is_damageable(otmp); } /* format a corpse name (xname() omits monster type; doname() calls us); @@ -3617,7 +3618,9 @@ readobjnam_preparse(struct _readobjnam_data *d) || !strncmpi(d->bp, "corrodeproof ", l = 13) || !strncmpi(d->bp, "fixed ", l = 6) || !strncmpi(d->bp, "fireproof ", l = 10) - || !strncmpi(d->bp, "rotproof ", l = 9)) { + || !strncmpi(d->bp, "rotproof ", l = 9) + || !strncmpi(d->bp, "tempered ", l = 9) + || !strncmpi(d->bp, "crackproof ", l = 11)) { d->erodeproof = 1; } else if (!strncmpi(d->bp, "lit ", l = 4) || !strncmpi(d->bp, "burning ", l = 8)) { @@ -3689,7 +3692,8 @@ readobjnam_preparse(struct _readobjnam_data *d) } else if (!strncmpi(d->bp, "rusty ", l = 6) || !strncmpi(d->bp, "rusted ", l = 7) || !strncmpi(d->bp, "burnt ", l = 6) - || !strncmpi(d->bp, "burned ", l = 7)) { + || !strncmpi(d->bp, "burned ", l = 7) + || !strncmpi(d->bp, "cracked ", l = 8)) { d->eroded = 1 + d->very; d->very = 0; } else if (!strncmpi(d->bp, "corroded ", l = 9) @@ -4846,7 +4850,8 @@ readobjnam(char *bp, struct obj *no_wish) if (erosion_matters(d.otmp)) { /* wished-for item shouldn't be eroded unless specified */ d.otmp->oeroded = d.otmp->oeroded2 = 0; - if (d.eroded && (is_flammable(d.otmp) || is_rustprone(d.otmp))) + if (d.eroded && (is_flammable(d.otmp) || is_rustprone(d.otmp) + || is_crackable(d.otmp))) d.otmp->oeroded = d.eroded; if (d.eroded2 && (is_corrodeable(d.otmp) || is_rottable(d.otmp))) d.otmp->oeroded2 = d.eroded2; diff --git a/src/potion.c b/src/potion.c index d18ed4430..296928717 100644 --- a/src/potion.c +++ b/src/potion.c @@ -2529,8 +2529,8 @@ potion_dip(struct obj *obj, struct obj *potion) } else if (obj->oclass != WEAPON_CLASS && !is_weptool(obj)) { /* the following cases apply only to weapons */ goto more_dips; - /* Oil removes rust and corrosion, but doesn't unburn. - * Arrows, etc are classed as metallic due to arrowhead + /* Oil removes rust and corrosion, but doesn't unburn or repair + * cracks. Arrows, etc are classed as metallic due to arrowhead * material, but dipping in oil shouldn't repair them. */ } else if ((!is_rustprone(obj) && !is_corrodeable(obj)) diff --git a/src/trap.c b/src/trap.c index e46694567..ec5175222 100644 --- a/src/trap.c +++ b/src/trap.c @@ -171,9 +171,10 @@ erode_obj( int ef_flags) { static NEARDATA const char - *const action[] = { "smoulder", "rust", "rot", "corrode" }, - *const msg[] = { "burnt", "rusted", "rotten", "corroded" }, - *const bythe[] = { "heat", "oxidation", "decay", "corrosion" }; + *const action[] = { "smoulder", "rust", "rot", "corrode", "crack" }, + *const msg[] = { "burnt", "rusted", "rotten", "corroded", "cracked" }, + *const bythe[] = { "heat", "oxidation", "decay", "corrosion", + "impact" }; /* this could use improvement... */ boolean vulnerable = FALSE, is_primary = TRUE, check_grease = (ef_flags & EF_GREASE) ? TRUE : FALSE, print = (ef_flags & EF_VERBOSE) ? TRUE : FALSE, @@ -219,6 +220,11 @@ erode_obj( is_primary = FALSE; cost_type = COST_CORRODE; break; + case ERODE_CRACK: /* crystal armor */ + vulnerable = is_crackable(otmp); + is_primary = TRUE; + cost_type = COST_CRACK; + break; default: impossible("Invalid erosion type in erode_obj"); return ER_NOTHING; @@ -288,13 +294,19 @@ erode_obj( return ER_DAMAGED; } else if (ef_flags & EF_DESTROY) { otmp->in_use = 1; /* in case of hangup during message w/ --More-- */ - if (uvictim || vismon || visobj) - pline("%s %s %s away!", + if (uvictim || vismon || visobj) { + char actbuf[BUFSZ]; + + if (cost_type != COST_CRACK) + Sprintf(actbuf, "%s away", vtense(ostr, action[type])); + else + Sprintf(actbuf, "shatters"); + pline("%s %s %s!", uvictim ? "Your" : !vismon ? "The" /* visobj */ : s_suffix(Monnam(victim)), - ostr, vtense(ostr, action[type])); - + ostr, actbuf); + } if (ef_flags & EF_PAY) costly_alteration(otmp, cost_type); diff --git a/src/zap.c b/src/zap.c index 89fb322c4..69541ca36 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1715,7 +1715,7 @@ poly_obj(struct obj *obj, int id) otmp->blessed = obj->blessed; if (erosion_matters(otmp)) { - if (is_flammable(otmp) || is_rustprone(otmp)) + if (is_flammable(otmp) || is_rustprone(otmp) || is_crackable(otmp)) otmp->oeroded = obj->oeroded; if (is_corrodeable(otmp) || is_rottable(otmp)) otmp->oeroded2 = obj->oeroded2; @@ -5144,7 +5144,9 @@ fracture_rock(struct obj *obj) /* no texts here! */ /* shop message says "you owe <$> for it!" so we need to precede that with a message explaining what "it" is */ You("fracture %s %s.", s_suffix(shkname(shkp)), xname(obj)); - breakobj(obj, x, y, TRUE, FALSE); /* charges for shop goods */ + /* breakobj won't destroy fracturing statue or boulder but + will charge for shop goods */ + (void) breakobj(obj, x, y, TRUE, FALSE); } } if (by_you && obj->otyp == BOULDER)