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 */ } }