From e9c58c2fe4277fc64433df3c9a2e8d954fd1d16a Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 14 Jun 2023 15:54:04 -0700 Subject: [PATCH] breaking crystal armor Instead of a 5% chance for crystal plate mail or crystal helmet to break each time it's subjected to breakage, switch to a 10% chance but the damage is treated as erosion rather than break/don't-break. 'crystal foo' will need to go through four stages of damage before breaking: cracked crystal foo, very cracked crystal foo, thoroughly cracked crystal foo, then gone. Crackproof handling is included, described as tempered crystal foo. It mostly still applies to throwing and kicking the item. Having some hits trigger damage might be worthwhile but isn't implemented. Object creation within lua code probably needs to be updated, and when the Mitre of Holiness is created in the priest/priestess quest it should start out as tempered (erodeproof). Perhaps it ought to be erodeproof regardless of where/how it's created. --- include/extern.h | 2 +- include/hack.h | 3 +- include/obj.h | 1 + include/objclass.h | 6 +++- src/dothrow.c | 75 +++++++++++++++++++++++++++------------------- src/makemon.c | 3 +- src/mkobj.c | 5 ++-- src/objnam.c | 37 +++++++++++++---------- src/potion.c | 4 +-- src/trap.c | 26 +++++++++++----- src/zap.c | 6 ++-- 11 files changed, 105 insertions(+), 63 deletions(-) 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)