From 28fb6fc67b40316356d7c6464e88cb611bf1b60a Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 22 Apr 2020 01:14:09 -0700 Subject: [PATCH] crystal ball enhancements Allow crystal ball to search for furniture (stairs and ladders, altar, throne, sink, fountain) as well as for a class or objects or of monsters or all traps. Giving any of '<','>','_','\','#', or '{' will find all of those rather than just the individual type specified. Because of the default character conflict, '_' can no longer be used to find chains; looking for altars is more useful. The chance of getting the cursed effect due to failing a saving throw against intelligence when the ball isn't actually cursed has been reduced. If it is the hero's own quest artifact, it will happen if rnd(8) is greater than Int, so Int of 8 or more will never yield that effect. Otherwise if it is blessed, rnd(16) is used so 16 or better Int means it can't act like it is cursed. When uncursed and not hero's quest artifact, the old rnd(20) > Int test is still used. Crystal balls now start with 3..7 charges rather than 1..5, and blessed charging sets the amount to 7 charges rather than 6 and also blesses the ball. Recharing with uncursed scroll of charging is slightly better (adds 1..2 charges instead of always just 1, caps the amount at 7 rather than 5) and uncurses the ball. Cursed scroll strips off all charges even if the ball is blessed and also curses the ball so is harsher than before. Crystal balls now cancel to -1 instead of 0, like wands, and using one effect will destroy it, like zapping cancelled wands. Also a minor tweak to the initial charges for can of grease (5..25 instead of 1..25) and horn of plenty and bag of tricks (both now 3..20 instead of 1..20). --- include/extern.h | 1 + src/detect.c | 141 +++++++++++++++++++++++++++++++++++++---------- src/drawing.c | 29 ++++++++++ src/mkobj.c | 6 +- src/read.c | 42 +++++++++++--- src/zap.c | 114 +++++++++++++++++++++----------------- 6 files changed, 243 insertions(+), 90 deletions(-) diff --git a/include/extern.h b/include/extern.h index 25d3ce40e..0f98657c2 100644 --- a/include/extern.h +++ b/include/extern.h @@ -596,6 +596,7 @@ E boolean FDECL(hurtle_step, (genericptr_t, int, int)); #endif /* !MAKEDEFS_C && !MDLIB_C && !LEV_LEX_C */ E int FDECL(def_char_to_objclass, (CHAR_P)); E int FDECL(def_char_to_monclass, (CHAR_P)); +E int FDECL(def_char_is_furniture, (CHAR_P)); #if !defined(MAKEDEFS_C) && !defined(MDLIB_C) && !defined(LEV_LEX_C) E void FDECL(switch_symbols, (int)); E void FDECL(assign_graphics, (int)); diff --git a/src/detect.c b/src/detect.c index 2fbd9aeac..427adbe23 100644 --- a/src/detect.c +++ b/src/detect.c @@ -20,6 +20,7 @@ static boolean FDECL(check_map_spot, (int, int, CHAR_P, unsigned)); static boolean FDECL(clear_stale_map, (CHAR_P, unsigned)); static void FDECL(sense_trap, (struct trap *, XCHAR_P, XCHAR_P, int)); static int FDECL(detect_obj_traps, (struct obj *, BOOLEAN_P, int)); +static int NDECL(furniture_detect); static void FDECL(show_map_spot, (int, int)); static void FDECL(findone, (int, int, genericptr_t)); static void FDECL(openone, (int, int, genericptr_t)); @@ -957,7 +958,8 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ else found = TRUE; } - if ((tr = detect_obj_traps(g.level.buriedobjlist, FALSE, 0)) != OTRAP_NONE) { + if ((tr = detect_obj_traps(g.level.buriedobjlist, FALSE, 0)) + != OTRAP_NONE) { if (tr & OTRAP_THERE) goto outtrapmap; else @@ -1039,6 +1041,56 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ return 0; } +static int +furniture_detect() +{ + struct monst *mon; + int x, y, glyph, sym, found = 0, revealed = 0; + + (void) unconstrain_map(); + + for (y = 0; y < ROWNO; ++y) + for (x = 1; x < COLNO; ++x) { + glyph = glyph_at(x, y); + sym = glyph_to_cmap(glyph); + if (IS_FURNITURE(levl[x][y].typ)) { + ++found; + magic_map_background(x, y, 1); + } else if (is_cmap_furniture(sym)) { + ++found; + if ((mon = m_at(x, y)) != 0 + && M_AP_TYPE(mon) == M_AP_FURNITURE) + seemimic(mon); + if (!mon || !canspotmon(mon)) + map_invisible(x, y); + } + if (glyph_at(x, y) != glyph) + ++revealed; + } + + if (!found) + pline("There seems to be nothing of interest on this level."); + else if (!revealed) + /* [what about clipped map with points of interest outside of the + currently shown area?] */ + Your("map already shows all relevant locations."); + + if (!revealed) + display_nhwindow(WIN_MAP, TRUE); + else /* we need to browse all types because we haven't redrawn the map + * with only points of interest */ + browse_map(TER_DETECT | TER_MAP | TER_TRP | TER_OBJ | TER_MON, + "location"); + + reconstrain_map(); + docrt(); /* redraw everything */ + if (Underwater) + under_water(2); + if (u.uburied) + under_ground(2); + return 0; +} + const char * level_distance(where) d_level *where; @@ -1082,7 +1134,15 @@ d_level *where; return "near you"; } -static const struct { + /* + * This could be made a lot more useful. Especially now that + * amnesia no longer causes levels to be forgotten. Perhaps a + * menu, and it ought to include the entrance to Vlad's Tower, + * one of the few things that requires active searching/mapping + * to find. And once the Wizard is in play, he is easy for the + * game to locate but not necessarily for the player. + */ +static const struct crystalballlevels { const char *what; d_level *where; } level_detects[] = { @@ -1099,25 +1159,28 @@ struct obj **optr; char ch; int oops; struct obj *obj = *optr; + boolean charged = (obj->spe > 0); if (Blind) { pline("Too bad you can't see %s.", the(xname(obj))); return; } - oops = (rnd(20) > ACURR(A_INT) || obj->cursed); - if (oops && (obj->spe > 0)) { - switch (rnd(obj->oartifact ? 4 : 5)) { + oops = is_quest_artifact(obj) ? 8 : obj->blessed ? 16 : 20; + if (charged && (obj->cursed || rnd(oops) > ACURR(A_INT))) { + long impair = (long) rnd(100 - 3 * ACURR(A_INT)); + + switch (rnd((obj->oartifact || obj->blessed) ? 4 : 5)) { case 1: pline("%s too much to comprehend!", Tobjnam(obj, "are")); break; case 2: pline("%s you!", Tobjnam(obj, "confuse")); - make_confused((HConfusion & TIMEOUT) + (long) rnd(100), FALSE); + make_confused((HConfusion & TIMEOUT) + impair, FALSE); break; case 3: if (!resists_blnd(&g.youmonst)) { pline("%s your vision!", Tobjnam(obj, "damage")); - make_blinded((Blinded & TIMEOUT) + (long) rnd(100), FALSE); + make_blinded((Blinded & TIMEOUT) + impair, FALSE); if (!Blind) Your1(vision_clears); } else { @@ -1127,8 +1190,8 @@ struct obj **optr; break; case 4: pline("%s your mind!", Tobjnam(obj, "zap")); - (void) make_hallucinated( - (HHallucination & TIMEOUT) + (long) rnd(100), FALSE, 0L); + (void) make_hallucinated((HHallucination & TIMEOUT) + impair, + FALSE, 0L); break; case 5: pline("%s!", Tobjnam(obj, "explode")); @@ -1145,8 +1208,14 @@ struct obj **optr; } if (Hallucination) { - if (!obj->spe) { + nomul(-rnd(charged ? 4 : 2)); + g.multi_reason = "gazing into a Magic 8-Ball (tm)"; + g.nomovemsg = ""; + + if (!charged) { pline("All you see is funky %s haze.", hcolor((char *) 0)); + if (obj->spe < 0) + goto implode; /* destroy it when it has been cancelled */ } else { switch (rnd(6)) { case 1: @@ -1178,7 +1247,7 @@ struct obj **optr; /* read a single character */ if (flags.verbose) - You("may look for an object or monster symbol."); + You("may look for an object, monster, or special map symbol."); ch = yn_function("What do you look for?", (char *) 0, '\0'); /* Don't filter out ' ' here; it has a use */ if ((ch != def_monsyms[S_GHOST].sym) && index(quitchars, ch)) { @@ -1186,12 +1255,26 @@ struct obj **optr; pline1(Never_mind); return; } + /* Possible extension: + * If ch=='?', ask whether player wants to find scrolls or is asking + * for help in using the crystal ball. + */ + You("peer into %s...", the(xname(obj))); - nomul(-rnd(10)); + nomul(-rnd(charged ? 10 : 2)); g.multi_reason = "gazing into a crystal ball"; g.nomovemsg = ""; - if (obj->spe <= 0) { + + if (!charged) { pline_The("vision is unclear."); + + if (obj->spe < 0) { /* destroy ball if used after being cancelled */ + implode: /* no damage to hero but 'multi' has a small negative value */ + pline("%s!", Tobjnam(obj, "implode")); + useup(obj); + *optr = obj = (struct obj *) 0; /* it's gone */ + return; + } } else { int class, i; int ret = 0; @@ -1205,25 +1288,25 @@ struct obj **optr; if (ch == DEF_MIMIC_DEF) ch = DEF_MIMIC; - if ((class = def_char_to_objclass(ch)) != MAXOCLASSES) + /* checking furnture before objects allows '_' to find altars + (along with other furniture) instead of finding iron chains */ + if (def_char_is_furniture(ch) >= 0) { + ret = furniture_detect(); + } else if ((class = def_char_to_objclass(ch)) != MAXOCLASSES) { ret = object_detect((struct obj *) 0, class); - else if ((class = def_char_to_monclass(ch)) != MAXMCLASSES) + } else if ((class = def_char_to_monclass(ch)) != MAXMCLASSES) { ret = monster_detect((struct obj *) 0, class); - else if (g.showsyms[SYM_BOULDER + SYM_OFF_X] - && (ch == g.showsyms[SYM_BOULDER + SYM_OFF_X])) + } else if (g.showsyms[SYM_BOULDER + SYM_OFF_X] + && (ch == g.showsyms[SYM_BOULDER + SYM_OFF_X])) { ret = object_detect((struct obj *) 0, ROCK_CLASS); - else - switch (ch) { - case '^': - ret = trap_detect((struct obj *) 0); - break; - default: - i = rn2(SIZE(level_detects)); - You_see("%s, %s.", level_detects[i].what, - level_distance(level_detects[i].where)); - ret = 0; - break; - } + } else if (ch == '^') { + ret = trap_detect((struct obj *) 0); + } else { + i = rn2(SIZE(level_detects)); + You_see("%s, %s.", level_detects[i].what, + level_distance(level_detects[i].where)); + ret = 0; + } if (ret) { if (!rn2(100)) /* make them nervous */ diff --git a/src/drawing.c b/src/drawing.c index 9b01bd460..ec3555cc9 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -269,6 +269,7 @@ def_char_to_objclass(ch) char ch; { int i; + for (i = 1; i < MAXOCLASSES; i++) if (ch == def_oc_syms[i].sym) break; @@ -285,12 +286,40 @@ def_char_to_monclass(ch) char ch; { int i; + for (i = 1; i < MAXMCLASSES; i++) if (ch == def_monsyms[i].sym) break; return i; } +/* does 'ch' represent a furniture character? returns index into defsyms[] */ +int +def_char_is_furniture(ch) +char ch; +{ + /* note: these refer to defsyms[] order which is much different from + levl[][].typ order but both keep furniture in a contiguous block */ + static const char first_furniture[] = "stair", /* "staircase up" */ + last_furniture[] = "fountain"; + int i; + boolean furniture = FALSE; + + for (i = 0; i < SIZE(defsyms); ++i) { + if (!furniture) { + if (!strncmpi(defsyms[i].explanation, first_furniture, 5)) + furniture = TRUE; + } + if (furniture) { + if (defsyms[i].sym == (uchar) ch) + return i; + if (!strcmpi(defsyms[i].explanation, last_furniture)) + break; /* reached last furniture */ + } + } + return -1; +} + #if !defined(CROSSCOMPILE) || defined(CROSSCOMPILE_TARGET) /* * Explanations of the functions found below: diff --git a/src/mkobj.c b/src/mkobj.c index bb875ddb5..dd7e92b09 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -941,16 +941,16 @@ boolean artif; otmp->spe = rn1(70, 30); break; case CAN_OF_GREASE: - otmp->spe = rnd(25); + otmp->spe = rn1(21, 5); /* 0..20 + 5 => 5..25 */ blessorcurse(otmp, 10); break; case CRYSTAL_BALL: - otmp->spe = rnd(5); + otmp->spe = rn1(5, 3); /* 0..4 + 3 => 3..7 */ blessorcurse(otmp, 2); break; case HORN_OF_PLENTY: case BAG_OF_TRICKS: - otmp->spe = rnd(20); + otmp->spe = rn1(18, 3); /* 0..17 + 3 => 3..20 */ break; case FIGURINE: tryct = 0; diff --git a/src/read.c b/src/read.c index 35f4cf8d4..d8062d569 100644 --- a/src/read.c +++ b/src/read.c @@ -644,17 +644,43 @@ int curse_bless; } break; case CRYSTAL_BALL: + if (obj->spe == -1) /* like wands, first uncancel */ + obj->spe = 0; + if (is_cursed) { - stripspe(obj); + /* cursed scroll removes charges and curses ball */ + /*stripspe(obj); -- doesn't do quite what we want...*/ + if (!obj->cursed) { + p_glow2(obj, NH_BLACK); + curse(obj); + } else { + pline("%s briefly.", Yobjnam2(obj, "vibrate")); + } + if (obj->spe > 0) + costly_alteration(obj, COST_UNCHRG); + obj->spe = 0; } else if (is_blessed) { - obj->spe = 6; - p_glow2(obj, NH_BLUE); + /* blessed scroll sets charges to max and blesses ball */ + obj->spe = 7; + p_glow2(obj, !obj->blessed ? NH_LIGHT_BLUE : NH_BLUE); + if (!obj->blessed) + bless(obj); + /* [shop price stays the same regardless of charges or BUC] */ } else { - if (obj->spe < 5) { - obj->spe++; - p_glow1(obj); - } else + /* uncursed scroll increments charges and uncurses ball */ + if (obj->spe < 7 || obj->cursed) { + n = rnd(2); + obj->spe = min(obj->spe + n, 7); + if (!obj->cursed) { + p_glow1(obj); + } else { + p_glow2(obj, NH_AMBER); + uncurse(obj); + } + } else { + /* charges at max and ball not being uncursed */ pline1(nothing_happens); + } } break; case HORN_OF_PLENTY: @@ -671,7 +697,7 @@ int curse_bless; obj->spe = 50; p_glow2(obj, NH_BLUE); } else { - obj->spe += rnd(5); + obj->spe += rn1(5, 2); if (obj->spe > 50) obj->spe = 50; p_glow1(obj); diff --git a/src/zap.c b/src/zap.c index a0d3807c1..0db7fff2b 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1031,63 +1031,77 @@ void cancel_item(obj) register struct obj *obj; { - boolean u_ring = (obj == uleft || obj == uright); int otyp = obj->otyp; - switch (otyp) { - case RIN_GAIN_STRENGTH: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_STR) -= obj->spe; - g.context.botl = 1; + if (carried(obj)) { + /* handle items being worn by hero */ + switch (otyp) { + case RIN_GAIN_STRENGTH: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_STR) -= obj->spe; + g.context.botl = TRUE; + } + break; + case RIN_GAIN_CONSTITUTION: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_CON) -= obj->spe; + g.context.botl = TRUE; + } + break; + case RIN_ADORNMENT: + if ((obj->owornmask & W_RING) != 0L) { + ABON(A_CHA) -= obj->spe; + g.context.botl = TRUE; + } + break; + case RIN_INCREASE_ACCURACY: + if ((obj->owornmask & W_RING) != 0L) + u.uhitinc -= obj->spe; + break; + case RIN_INCREASE_DAMAGE: + if ((obj->owornmask & W_RING) != 0L) + u.udaminc -= obj->spe; + break; + case RIN_PROTECTION: + if ((obj->owornmask & W_RING) != 0L) + g.context.botl = TRUE; + break; + case GAUNTLETS_OF_DEXTERITY: + if ((obj->owornmask & W_ARMG) != 0L) { + ABON(A_DEX) -= obj->spe; + g.context.botl = TRUE; + } + break; + case HELM_OF_BRILLIANCE: + if ((obj->owornmask & W_ARMH) != 0L) { + ABON(A_INT) -= obj->spe; + ABON(A_WIS) -= obj->spe; + g.context.botl = TRUE; + } + break; + default: + if ((obj->owornmask & W_ARMOR) != 0L) /* AC */ + g.context.botl = TRUE; + break; } - break; - case RIN_GAIN_CONSTITUTION: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_CON) -= obj->spe; - g.context.botl = 1; - } - break; - case RIN_ADORNMENT: - if ((obj->owornmask & W_RING) && u_ring) { - ABON(A_CHA) -= obj->spe; - g.context.botl = 1; - } - break; - case RIN_INCREASE_ACCURACY: - if ((obj->owornmask & W_RING) && u_ring) - u.uhitinc -= obj->spe; - break; - case RIN_INCREASE_DAMAGE: - if ((obj->owornmask & W_RING) && u_ring) - u.udaminc -= obj->spe; - break; - case GAUNTLETS_OF_DEXTERITY: - if ((obj->owornmask & W_ARMG) && (obj == uarmg)) { - ABON(A_DEX) -= obj->spe; - g.context.botl = 1; - } - break; - case HELM_OF_BRILLIANCE: - if ((obj->owornmask & W_ARMH) && (obj == uarmh)) { - ABON(A_INT) -= obj->spe; - ABON(A_WIS) -= obj->spe; - g.context.botl = 1; - } - break; - /* case RIN_PROTECTION: not needed */ } + /* cancelled item might not be in hero's possession but + cancellation is presumed to be instigated by hero */ if (objects[otyp].oc_magic || (obj->spe && (obj->oclass == ARMOR_CLASS || obj->oclass == WEAPON_CLASS || is_weptool(obj))) || otyp == POT_ACID || otyp == POT_SICKNESS || (otyp == POT_WATER && (obj->blessed || obj->cursed))) { - if (obj->spe != ((obj->oclass == WAND_CLASS) ? -1 : 0) + int cancelled_spe = (obj->oclass == WAND_CLASS + || obj->otyp == CRYSTAL_BALL) ? -1 : 0; + + if (obj->spe != cancelled_spe && otyp != WAN_CANCELLATION /* can't cancel cancellation */ && otyp != MAGIC_LAMP /* cancelling doesn't remove djinni */ && otyp != CANDELABRUM_OF_INVOCATION) { costly_alteration(obj, COST_CANCEL); - obj->spe = (obj->oclass == WAND_CLASS) ? -1 : 0; + obj->spe = cancelled_spe; } switch (obj->oclass) { case SCROLL_CLASS: @@ -1103,10 +1117,8 @@ register struct obj *obj; } break; case POTION_CLASS: - costly_alteration(obj, - (otyp != POT_WATER) - ? COST_CANCEL - : obj->cursed ? COST_UNCURS : COST_UNBLSS); + costly_alteration(obj, (otyp != POT_WATER) ? COST_CANCEL + : obj->cursed ? COST_UNCURS : COST_UNBLSS); if (otyp == POT_SICKNESS || otyp == POT_SEE_INVISIBLE) { /* sickness is "biologically contaminated" fruit juice; cancel it and it just becomes fruit juice... @@ -2759,10 +2771,10 @@ register struct monst *mdef; register struct obj *obj; boolean youattack, allow_cancel_kill, self_cancel; { + static const char + writing_vanishes[] = "Some writing vanishes from %s head!", + your[] = "your"; /* should be extern */ boolean youdefend = (mdef == &g.youmonst); - static const char writing_vanishes[] = - "Some writing vanishes from %s head!"; - static const char your[] = "your"; /* should be extern */ if (youdefend ? (!youattack && Antimagic) : resist(mdef, obj->oclass, 0, NOTELL)) @@ -2774,9 +2786,11 @@ boolean youattack, allow_cancel_kill, self_cancel; for (otmp = (youdefend ? g.invent : mdef->minvent); otmp; otmp = otmp->nobj) cancel_item(otmp); + if (youdefend) { g.context.botl = 1; /* potential AC change */ find_ac(); + /* update_inventory(); -- handled by caller */ } }