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).
This commit is contained in:
PatR
2020-04-22 01:14:09 -07:00
parent b7030d1567
commit 28fb6fc67b
6 changed files with 243 additions and 90 deletions

View File

@@ -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));

View File

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

View File

@@ -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:

View File

@@ -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;

View File

@@ -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);

114
src/zap.c
View File

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